Imports VB = Microsoft.VisualBasic
Imports System.Windows.Forms

Public Class formMain
#Region "Class Globals"
    Dim mstrROMFilePath As String                   'Path to the .NES file being modded
    Dim mabytFileData() As Byte                     'The entire ROM's file contents
    Dim maintScreenHeaderAddressData(,) As Integer  'contains the addresses for the beginning of each screens' spawn info for each level
    Dim maLevelData() As LevelStruct                'Contains unpacked level tile background info
    Dim maPatternTable() As PatternTableStruct      'Contains both BG and sprite pattern tables
    Dim maF2PatternTable() As PatternTableStruct    'Contains both BG and sprite pattern tables (includes tiles overwritten by the F2 object - usually for bosses)
    Dim mabolF2Tiles(,) As Boolean                  'For each level, determines whether the given tile index uses F2 graphics or default level graphics - usually boss tiles
    Dim mbolMouseDown As Boolean                    'Determines if the mouse was clicked.  Used to drag and file the same background tile info quickly.
    Dim maradLevel() As RadioButton                 'Radio button array for choosing whch level to mod easily
    Dim mgfxLevel As Graphics                       'Graphics to the Level picturebox
    Dim mgfxObject As Graphics                      'Graphics to the Object picturebox
    Dim maPaletteData() As PaletteStruct            'Contains the BG and sprite palette colors
    Dim maHelipadPaletteData() As HelipadStruct     'Contains the 3 palette tables for the flashing helipad lights on each level
    Dim maF2PaletteData() As F2PaletteStruct        'Contains the palette info that is updated when the F2 object is encountered in each level (Level 2 has no F2)
    Dim mbolNotSaved As Boolean = False             'Used to prevent the form from closing without saving a modified ROM
    Dim mintLastObjectLocationInLevel As Integer    'Tracks the last tile position of the selected object inside the level.  This is used to redraw the level when the object highlight box is removed
    Dim mrectLastObjectSelected As Rectangle = New Rectangle(New Point(0, 0), New Size(0, 0))   'This tracks the last object selected in the object spawn list.
#End Region

#Region "Form"
    Private Sub formMain_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Initialize()
        OpenROM()
    End Sub

    Private Sub formMain_MouseWheel(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseWheel
        'Scrolls the Level picture box.  A custom radio button was created to not consume the wheel mouse input
        If e.Delta < 0 Then 'Get the mouse wheel direction
            pnlLevel.AutoScrollPosition = New Point(pnlLevel.AutoScrollPosition.X, Math.Min(pnlLevel.VerticalScroll.Maximum, pnlLevel.VerticalScroll.Value + 256))
        Else
            pnlLevel.AutoScrollPosition = New Point(pnlLevel.AutoScrollPosition.X, Math.Max(pnlLevel.VerticalScroll.Value - 256, pnlLevel.VerticalScroll.Minimum))
        End If
    End Sub

    Private Sub formMain_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
        Dim lngResult As MsgBoxResult

        'Check to see if the ROM has been modified
        If mbolNotSaved = True Then
            'Notify the user that they will lose changes
            lngResult = MsgBox("The ROM file may have changed but has not been saved.  Are you sure you want to quit?", vbYesNo)

            If lngResult = MsgBoxResult.No Then e.Cancel = True 'Don't close if the user selects 'No'
        End If
    End Sub

    ''' <summary>
    ''' 
    ''' </summary>
    ''' <param name="msg"></param>
    ''' <param name="keyData"></param>
    ''' <returns></returns>
    ''' <remarks>I had to use this instead of the KeyDown event</remarks>
    Protected Overrides Function ProcessCmdKey(ByRef msg As Message, keyData As Keys) As Boolean
        Dim astrTagParams() As String   'Used to get the Tag information from the picSelected object
        Dim intIndex As Integer         'Used to narrow down the position of the sprite being moved so only a portion of the level is redrawn
        Dim intKeyData As Integer = keyData   'Key user has pressed
        Dim intOffset As Integer = 1    'Distance to move sprite per arrow press

        'SHIFT + arrow keys
        'Shift is used to move a sprite a large distance per arrow press
        If intKeyData = &H10025 Then
            intKeyData = &H25
            intOffset = 8
        ElseIf intKeyData = &H10026 Then
            intKeyData = &H26
            intOffset = 8
        ElseIf intKeyData = &H10027 Then
            intKeyData = &H27
            intOffset = 8
        ElseIf intKeyData = &H10028 Then
            intKeyData = &H28
            intOffset = 8
        End If

        If intKeyData = 131151 Then 'CTRL-O
            OpenROM()
        ElseIf intKeyData = 131155 Then 'CTRL-S
            SaveROM()
        ElseIf intKeyData = 196691 Then 'CTRL-SHIFT-S
            SaveROMAs()
            '         LEFT                UP                  RIGHT               DOWN     (for moving the object around incrementally)
        ElseIf intKeyData = &H25 Or intKeyData = &H26 Or intKeyData = &H27 Or intKeyData = &H28 Then
            astrTagParams = Split(picSelected.Tag, ",") 'Read the rag data (TILE or OBJECT), and ID

            If picSelected.Image IsNot Nothing Then 'Make sure something is selected first
                If astrTagParams(0) = "OBJECT" Then 'Only process the arrow if an object is selected.  Arrows have no function for tiles.
                    intIndex = Int(gaObjects(gintLevel).LevelObjects(astrTagParams(1)).X / 8)
                    intIndex = intIndex + Int((gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y) / 32) * 16

                    'Only reload the level immediately around the affected object.  saves execution time and doesn't flicker the picture box as bad
                    'We need to do this before changing the (x,y) position to make sure any old sprite graphics are erased
                    LoadLevel(intIndex)

                    If intKeyData = &H25 Then       'Move Left
                        If isPlacementOK(Math.Max(0, gaObjects(gintLevel).LevelObjects(astrTagParams(1)).X - intOffset), gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y) Then
                            gaObjects(gintLevel).LevelObjects(astrTagParams(1)).X = Math.Max(0, gaObjects(gintLevel).LevelObjects(astrTagParams(1)).X - intOffset)
                        End If
                    ElseIf intKeyData = &H26 Then   'Move Up
                        If isPlacementOK(gaObjects(gintLevel).LevelObjects(astrTagParams(1)).X, Math.Min(picLevel.Height, gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y + intOffset)) Then
                            gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y = Math.Min(picLevel.Height, gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y + intOffset)
                        Else
                            'See if skipping &HEF to &HFF region is possible
                            If isPlacementOK(gaObjects(gintLevel).LevelObjects(astrTagParams(1)).X, Math.Min(picLevel.Height, gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y + intOffset + &H11)) Then
                                gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y = Math.Min(picLevel.Height, gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y + intOffset + &H11)
                            End If
                        End If
                    ElseIf intKeyData = &H27 Then   'Move Right
                        If isPlacementOK(Math.Min(&H7F, gaObjects(gintLevel).LevelObjects(astrTagParams(1)).X + intOffset), gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y) Then
                            gaObjects(gintLevel).LevelObjects(astrTagParams(1)).X = Math.Min(&H7F, gaObjects(gintLevel).LevelObjects(astrTagParams(1)).X + intOffset)
                        End If
                    ElseIf intKeyData = &H28 Then   'Move Down
                        'No objects can exist on screen 1 of Level 1
                        If isPlacementOK(gaObjects(gintLevel).LevelObjects(astrTagParams(1)).X, Math.Max(0, gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y - intOffset)) Then
                            gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y = Math.Max(0, gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y - intOffset)
                        Else
                            'See if skipping &HEF to &HFF region is possible
                            If isPlacementOK(gaObjects(gintLevel).LevelObjects(astrTagParams(1)).X, Math.Max(0, gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y - intOffset - &H11)) Then
                                gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y = Math.Max(0, gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y - intOffset - &H11)
                            End If
                        End If
                    End If

                    'Update the object's information and messages
                    lblObjectX.Text = Hex(gaObjects(gintLevel).LevelObjects(astrTagParams(1)).X)
                    lblObjectY.Text = Hex(gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y)
                    lblOddY.Visible = gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y Mod 2
                    lblSpawnsAtBottom.Visible = gaObjects(gintLevel).LevelObjects(astrTagParams(1)).SpawnAtBottom

                    LoadObjectsToLevel()    'Reloads every object.  Not intensive, but only reloading just the objects nearby would improve performance
                End If
            End If
        End If

        Return MyBase.ProcessCmdKey(msg, keyData)   'Finish processing the key press normally
    End Function
#End Region

#Region "Mouse Events"
    Private Sub picLevel_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles picLevel.MouseDown
        Dim intIndexX As Integer        'X Tile coordinate where the user clicked
        Dim intIndexY As Integer        'Y Tile coordinate where the user clicked
        Dim intIndex As Integer         'Calculated level tile position
        Dim astrTagParams() As String   'Tag attributes
        Dim bytLargeTileValue As Byte   'Holds the index position of the selected tile

        mbolNotSaved = True 'Indicate ROM has changed

        intIndexX = Int(e.X / 32) '32x32 pixels per object
        intIndexY = Int(e.Y / 32)

        If e.Button = System.Windows.Forms.MouseButtons.Left Then
            astrTagParams = Split(picSelected.Tag, ",")

            If picSelected.Image IsNot Nothing Then 'Make sure something is selected
                If e.X < picLevel.Width And e.Y < picLevel.Height And e.X > 0 And e.Y > 0 Then  'Make sure we are within the bounds of the picturebox
                    If astrTagParams(0) = "TILE" Then   'Overwrites the tile under the mouse cursor with the selected tile
                        maLevelData(gintLevel).LevelData((Int(picLevel.Height / 32) - intIndexY - 1) * 16 + intIndexX) = astrTagParams(1)
                        LoadLevel((Int(picLevel.Height / 32) - intIndexY - 1) * 16 + intIndexX) 'Reload the level in the immediate vacinity of the edited tile
                        LoadObjectsToLevel()    'Reload ALL objects.  not efficient but not intesive either
                    ElseIf astrTagParams(0) = "OBJECT" Then
                        With gaObjects(gintLevel)
                            If isPlacementOK((e.X / 4), picLevel.Height - e.Y) Then
                                'Reload the level where the object USED TO BE.  required to erase the old object graphic
                                intIndex = Int(.LevelObjects(astrTagParams(1)).X / 8)
                                intIndex = intIndex + Int((.LevelObjects(astrTagParams(1)).Y) / 32) * 16

                                LoadLevel(intIndex)

                                'Set the objects new position and reload this part of the level as well
                                .LevelObjects(astrTagParams(1)).X = Int(e.X / 4)
                                .LevelObjects(astrTagParams(1)).Y = picLevel.Height - e.Y

                                intIndex = Int(.LevelObjects(astrTagParams(1)).X / 8)
                                intIndex = intIndex + Int((.LevelObjects(astrTagParams(1)).Y) / 32) * 16
                                LoadLevel(intIndex)

                                LoadObjectsToLevel()    'Reload ALL objects.  not efficient but not intesive either

                                'Update the object's information and messages
                                lblObjectX.Text = Hex(gaObjects(gintLevel).LevelObjects(astrTagParams(1)).X)
                                lblObjectY.Text = Hex(gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y)
                                lblOddY.Visible = gaObjects(gintLevel).LevelObjects(astrTagParams(1)).Y Mod 2
                                lblSpawnsAtBottom.Visible = gaObjects(gintLevel).LevelObjects(astrTagParams(1)).SpawnAtBottom

                            End If
                        End With
                    End If
                End If
            End If

            mbolMouseDown = True    'Flag to move mouse and keep placing the same BG tile
        ElseIf e.Button = System.Windows.Forms.MouseButtons.Right Then  'A right click will select the tile under the mouse cursor for placement elsewhere
            'Instead of finding th BG Tile in picTile, the user can right click the picLevel graphic and select the tile to use elsewhere
            If e.X < picLevel.Width And e.Y < picLevel.Height And e.X > 0 And e.Y > 0 Then  'Make sure we are within the bounds of the picturebox
                bytLargeTileValue = maLevelData(gintLevel).LevelData((Int(picLevel.Height / 32) - intIndexY - 1) * 16 + intIndexX)  'Find the index of the tile selected (in the internal array)
                'Calculate the position of this tile in the picturebox for the tiles
                intIndexY = Int(bytLargeTileValue / 8)
                intIndexX = bytLargeTileValue - intIndexY * 8
                'Simulate the user clicking this tile in the tiles picturebox
                picTile_MouseDown(Nothing, New MouseEventArgs(MouseButtons.Left, 1, intIndexX * 32, intIndexY * 32, 0)) 'Tile selection
            End If
        End If
    End Sub

    Private Sub picLevel_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles picLevel.MouseUp
        mbolMouseDown = False   'reset flag for holding mouse button and dragging to place lots of the same tile
    End Sub

    Private Sub picLevel_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles picLevel.MouseMove
        Static intLastX As Integer
        Static intlastY As Integer

        'Detect when the mouse moves to a different 32x32 tile
        If Int(e.X / 32) <> intLastX Or Int(e.Y / 32) <> intlastY Then
            If mbolMouseDown Then   'If the left mouse button is held down
                If VB.Left(picSelected.Tag, 4) = "TILE" Then    'This has no function if an object is selected
                    picLevel_MouseDown(sender, e)   'Simulate the user clicking to place another tile
                End If
            End If
        End If

        'Update the 'last clicked' variables
        intLastX = Int(e.X / 32)
        intlastY = Int(e.Y / 32)
    End Sub

    Private Sub picTile_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles picTile.MouseDown
        Dim intIndexX As Integer
        Dim intIndexY As Integer

        'Calculate which tile the mouse is over
        intIndexX = Int(e.X / 32)
        intIndexY = Int(e.Y / 32)

        'Copy the tile from where the user clicked it, and put it in the selected box (double size)
        picSelected.Image = ImageSubset(picTile.Image, New Rectangle(New Point(0, 0), New Point(64, 64)), New Rectangle(intIndexX * 32, intIndexY * 32, 32, 32))
        picSelected.Tag = "TILE," & intIndexY * 8 + intIndexX
        grpSelection.Text = "Selection - BG Tile"   'Update the text to indicate to the user what type of item is selected

        LoadLevel(mintLastObjectLocationInLevel) 'This erases the box around the last enemy selected on the level graphic
        mrectLastObjectSelected = New Rectangle(New Point(0, 0), New Size(0, 0))    'Erase memory of the last object selected
        LoadObjectsToLevel()    'The objects are erase when the level loads, so this must occur
        ResetObjectDisplayXY()  'No object is selected so erase the object information
    End Sub

    Private Sub picObject_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles picObject.MouseDown
        Dim intIndexX As Integer
        Dim intIndexY As Integer
        Dim bmpObject As Bitmap
        Dim form As formObject = New formObject
        Dim intIndex As Integer
        Dim intRectX As Integer
        Dim intRectY As Integer

        mbolNotSaved = True 'Something changed.  set the need to save flag

        'Calculate the index of the object under the mouse cursor
        intIndexX = Int(e.X / 32)
        intIndexY = Int(e.Y / 32)

        If intIndexX + intIndexY * 8 <= UBound(gaObjects(gintLevel).LevelObjects) Then
            'Scroll the level to the object
            pnlLevel.AutoScrollPosition = New Point(0, picLevel.Height - gaObjects(gintLevel).LevelObjects(intIndexX + (intIndexY * 8)).Y - 400)    '400 is approximately half the visible level height

            'Draw a box around the active object
            intRectX = (gaObjects(gintLevel).LevelObjects(intIndexX + (intIndexY * 8)).X + gaObjects(gintLevel).Offset(FindObjectInConversionTable(gaObjects(gintLevel).LevelObjects(intIndexX + (intIndexY * 8)).ObjectValue)).X) * 4 - 16
            intRectY = picLevel.Height - gaObjects(gintLevel).LevelObjects(intIndexX + (intIndexY * 8)).Y + gaObjects(gintLevel).Offset(FindObjectInConversionTable(gaObjects(gintLevel).LevelObjects(intIndexX + (intIndexY * 8)).ObjectValue)).Y - 32

            LoadLevel(mintLastObjectLocationInLevel) 'This is used to erase the box around the enemy on the level
            LoadObjectsToLevel()        'This must be done after the level tiles above are reloaded to erase the box
            mgfxLevel.DrawRectangle(New Pen(New SolidBrush(Color.HotPink), 2), New Rectangle(New Point(intRectX, intRectY), New Size(32, 32)))  'Draw the box around the object

            'Record the tile position of the new box
            mintLastObjectLocationInLevel = Int(gaObjects(gintLevel).LevelObjects(intIndexX + intIndexY * 8).X / 8)
            mintLastObjectLocationInLevel = mintLastObjectLocationInLevel + Int((gaObjects(gintLevel).LevelObjects(intIndexX + intIndexY * 8).Y) / 32) * 16

            If e.Button = MouseButtons.Left Then    'A left click will open the object edit form
                'Center the new form on the current form
                form.FormLeft = Me.Left + Me.Width / 2
                form.FormTop = Me.Top + Me.Height / 2
                form.intObjectIndex = intIndexX + intIndexY * 8
                form.ShowDialog()

                If form.bmpObject IsNot Nothing Then
                    bmpObject = form.bmpObject
                    gaObjects(gintLevel).LevelObjects(intIndexX + intIndexY * 8).ObjectValue = form.intObjectValue

                    intIndex = Int(gaObjects(gintLevel).LevelObjects(intIndexX + intIndexY * 8).X / 8)
                    intIndex = intIndex + Int((gaObjects(gintLevel).LevelObjects(intIndexX + intIndexY * 8).Y) / 32) * 16

                    LoadLevel(intIndex)
                    LoadObjectsToLevel()

                    bmpObject.MakeTransparent(gcclrObjectBackground)
                    picSelected.Image = ImageSubset(bmpObject, New Rectangle(New Point(0, 0), New Point(64, 64)), New Rectangle(0, 0, 32, 32))
                End If
                'ElseIf e.Button = MouseButtons.Right Then
            End If

            bmpObject = ImageSubset(picObject.Image, New Rectangle(New Point(0, 0), New Point(64, 64)), New Rectangle(intIndexX * 32, intIndexY * 32, 32, 32))
            bmpObject.MakeTransparent(gcclrObjectBackground)
            picSelected.Image = bmpObject
            'End If

            picSelected.Tag = "OBJECT," & (intIndexX + intIndexY * 8)
            grpSelection.Text = "Selection - Event"

            mrectLastObjectSelected = New Rectangle(New Point(Int(e.X / 32) * 32, Int(e.Y / 32) * 32), New Size(32, 32))
            LoadObjectsToLevel()

            lblObjectX.Text = Hex(gaObjects(gintLevel).LevelObjects((intIndexX + intIndexY * 8)).X)
            lblObjectY.Text = Hex(gaObjects(gintLevel).LevelObjects((intIndexX + intIndexY * 8)).Y)
            lblOddY.Visible = gaObjects(gintLevel).LevelObjects((intIndexX + intIndexY * 8)).Y Mod 2
            lblSpawnsAtBottom.Visible = gaObjects(gintLevel).LevelObjects((intIndexX + intIndexY * 8)).SpawnAtBottom

            'picObject.Refresh()
            picLevel.Refresh()
        End If
    End Sub

    Private Sub picObject_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles picObject.MouseMove
        Dim intIndex As Integer = Int(e.X / 32) + (Int(e.Y / 32) * 8)
        Try
            If tip.GetToolTip(picObject) <> "ID:" & Hex(gaObjects(gintLevel).LevelObjects(intIndex).ObjectValue) & ",X:" & Hex(gaObjects(gintLevel).LevelObjects(intIndex).X) & ",Y:" & Hex(gaObjects(gintLevel).LevelObjects(intIndex).Y) & ",Spawn:" & IIf(gaObjects(gintLevel).LevelObjects(intIndex).SpawnAtBottom, "Bottom", "Top") Then
                tip.SetToolTip(picObject, "ID:" & Hex(gaObjects(gintLevel).LevelObjects(intIndex).ObjectValue) & ",X:" & Hex(gaObjects(gintLevel).LevelObjects(intIndex).X) & ",Y:" & Hex(gaObjects(gintLevel).LevelObjects(intIndex).Y) & ",Spawn:" & IIf(gaObjects(gintLevel).LevelObjects(intIndex).SpawnAtBottom, "Bottom", "Top"))
            End If
        Catch ex As Exception
            '!!!!
        End Try
    End Sub
#End Region

#Region "Menu"
    Private Sub tsmFileOpen_Click(sender As Object, e As EventArgs) Handles tsmFileOpen.Click
        OpenROM()
    End Sub

    Private Sub tsmFileSave_Click(sender As Object, e As EventArgs) Handles tsmFileSave.Click
        SaveROM()
    End Sub

    Private Sub tsmFileSaveas_Click(sender As Object, e As EventArgs) Handles tsmFileSaveas.Click
        SaveROMAs()
    End Sub

    ''' <summary>
    ''' The help file is included in this project, but any help file must be launched from a physical file.  So we copy the information to a file in the TEMP folder and launch it
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub tsmHelp_Click(sender As Object, e As EventArgs) Handles tsmHelp.Click
        Dim strHelpFile As String = "Help.htm"
        Dim strHelpPath As String = System.IO.Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.System)) & "TEMP\Jackal Level Editor\"

        'Create the directory if it doesn't exist
        If Not System.IO.Directory.Exists(strHelpPath) Then
            System.IO.Directory.CreateDirectory(strHelpPath)
        End If

        'Save the two files necessary to run the help file
        SaveImageAs(My.Resources.LE, strHelpPath & "\LE.jpg", 100, System.Drawing.Imaging.ImageFormat.Jpeg)
        System.IO.File.WriteAllText(strHelpPath & "\" & strHelpFile, My.Resources.Help)

        Process.Start(strHelpPath & "\" & strHelpFile)  'Launch the help file
    End Sub
#End Region

#Region "Options"
    Private Sub chkGridLines_CheckedChanged(sender As Object, e As EventArgs) Handles chkGridLines.CheckedChanged
        'Reload the level
        If radLevel1.Checked Then radLevel_Changed(0)
        If radLevel2.Checked Then radLevel_Changed(1)
        If radLevel3.Checked Then radLevel_Changed(2)
        If radLevel4.Checked Then radLevel_Changed(3)
        If radLevel5.Checked Then radLevel_Changed(4)
        If radLevel6.Checked Then radLevel_Changed(5)
    End Sub

    Private Sub chkGrayLines_CheckedChanged(sender As Object, e As EventArgs) Handles chkGrayLines.CheckedChanged
        LoadLevel()
        LoadObjectsToLevel()
    End Sub
#End Region

#Region "To/From ROM"
    Private Sub BuildContiguousLevelData()
        Dim lngIndex As Long
        Dim intEnd As Integer

        'NOTE: the first row of each 128 block only shows the top 2 rows (8 8x8 tiles) instead of all 4 rows of each 32x32 tile, but the map data is still relevant.
        'The very first row of each level only shows the top row (4 8x8 tiles) of each 32x32 tile.

        For k As Integer = 0 To 5  '6 levels
            lngIndex = 0
            intEnd = IIf(k = 5, 13, 11) 'The last screen index
            For j As Integer = 0 To intEnd  '12 blocks of 128 bytes
                For i As Integer = gacintLevelLargeTileDataHeaderAddress(k) To (gacintLevelLargeTileDataHeaderAddress(k) + &H7F)
                    maLevelData(k).LevelData(lngIndex) = mabytFileData(i - &H80 * j)
                    lngIndex = lngIndex + 1
                Next i
            Next j
        Next k
    End Sub

    Private Sub PackContiguousLevelDatatoROM()
        Dim Index As Long

        'NOTE: the first row of each 128 block only shows the top 4 8x8 squares instead of all 16, but the map data is still relevant
        For k As Integer = 0 To 5
            Index = 0
            For j As Integer = 0 To IIf(k = 5, 13, 11)  '12 blocks of 128 bytes.  14 for level 6
                For i As Integer = gacintLevelLargeTileDataHeaderAddress(k) To gacintLevelLargeTileDataHeaderAddress(k) + &H7F
                    mabytFileData(i - &H80 * j) = maLevelData(k).LevelData(Index)
                    Index = Index + 1
                Next i
            Next j
        Next k
    End Sub

    Private Sub BuildObjectData()
        Dim intIndex As Integer
        Dim intOffset As Integer
        Dim intObjectIndex As Integer
        Dim intNumScreens As Integer = 11   '12/14 screens, but final boss screen is not editable

        For k As Integer = 0 To 5   '6 levels
            If k = 5 Then intNumScreens = 13 'Level 6 has two additional screens
            intOffset = 0

            For i As Integer = 0 To intNumScreens
                'build the address of the beginning of the sprite data.
                '+ &H10000 because the sprite spawn data is in bank 7 (starts at &H18000)
                '+ &H10 because the .NES file has a 16 byte header
                intIndex = mabytFileData(gacintLevelScreenHeaderStartAddress(k) + (i * 2)) + mabytFileData(gacintLevelScreenHeaderStartAddress(k) + ((i * 2) + 1)) * &H100 + (&H10000 + &H10)

                Do While mabytFileData(intIndex) <> &HEF
                    'palette shift and other unknown commands (F0, F1, and F2).  consumes 2 bytes total.
                    If isSpecialObject(mabytFileData(intIndex)) Then
                        intObjectIndex = AutoRedim(gaObjects(k).LevelObjects)
                        gaObjects(k).LevelObjects(intObjectIndex).Y = intOffset + mabytFileData(intIndex + 1)
                        gaObjects(k).LevelObjects(intObjectIndex).X = 0
                        gaObjects(k).LevelObjects(intObjectIndex).ObjectValue = mabytFileData(intIndex)
                        intIndex = intIndex + 2 '2 bytes consumed here
                    Else
                        intObjectIndex = AutoRedim(gaObjects(k).LevelObjects)
                        gaObjects(k).LevelObjects(intObjectIndex).Y = intOffset + mabytFileData(intIndex)
                        gaObjects(k).LevelObjects(intObjectIndex).X = mabytFileData(intIndex + 1)
                        gaObjects(k).LevelObjects(intObjectIndex).ObjectValue = mabytFileData(intIndex + 2)

                        If gaObjects(k).LevelObjects(intObjectIndex).X >= &H80 Then 'determine if the sprite spawns at the bottom of the screen
                            gaObjects(k).LevelObjects(intObjectIndex).X = gaObjects(k).LevelObjects(intObjectIndex).X - &H80
                            gaObjects(k).LevelObjects(intObjectIndex).SpawnAtBottom = True
                        End If

                        intIndex = intIndex + 3 '3 bytes consumed here
                    End If
                Loop

                intOffset = intOffset + &H100   'Add &H100 for every screen to track where the sprites are spawning
            Next i
        Next k
    End Sub

    Private Sub BuildPatternTables()
        Dim lstbytData As List(Of Byte)
        Dim lngIndex As Long
        Dim bytBank As Byte
        Dim intROMAddress As Integer
        Dim lngAddress As Long
        Dim abytTEMP() As Byte

        For i As Integer = 0 To 5
            lngIndex = gacintLevelPatternTableAddress(i)
            lstbytData = New List(Of Byte)

            Do While mabytFileData(lngIndex) <> &HFF
                bytBank = mabytFileData(lngIndex)
                intROMAddress = mabytFileData(lngIndex + 1) + mabytFileData(lngIndex + 2) * &H100
                lngIndex += 3

                'Calculate the position in ROM where the data is stored (based on the mapper).  Add &H10 for the .NES header
                lngAddress = (intROMAddress - &H8000) + (&H4000 * bytBank) + &H10

                'Copy from the start address to the end of the ROM (because we don't know where the end is without decompressing it first!)
                ReDim abytTEMP(UBound(mabytFileData) - lngAddress)
                Array.Copy(mabytFileData, lngAddress + 2, abytTEMP, 0, UBound(mabytFileData) - (lngAddress + 2))    'Offset by 2 because PPU address are the first 2 bytes
                lstbytData.AddRange(DecompressKonamiRLE(abytTEMP))  'Normal graphics data is RLE compressed
            Loop

            maPatternTable(i).PatternTableData = lstbytData.ToArray

            'Create the F2 Pattern Table
            maF2PatternTable(i).PatternTableData = maPatternTable(i).PatternTableData.Clone
        Next i

        BuildF2PatternTables()
    End Sub

    Private Sub BuildF2PatternTables()
        Dim lngROMIndex As Long
        Dim lngVRAMIndex As Long
        Dim lngByteCount As Long

        For i As Integer = 0 To 5
            If gacintLevelF2LargeTileDefinitionAddress(i) <> 0 Then
                lngVRAMIndex = (mabytFileData(gacintLevelF2LargeTileDefinitionAddress(i)) * &H100) + (mabytFileData(gacintLevelF2LargeTileDefinitionAddress(i) + 1))
                lngByteCount = (mabytFileData(gacintLevelF2LargeTileDefinitionAddress(i) + 4) * &H100) + (mabytFileData(gacintLevelF2LargeTileDefinitionAddress(i) + 5))
                lngROMIndex = gacintLevelF2LargeTileDefinitionAddress(i) + 6

                For j As Long = 1 To lngByteCount
                    maF2PatternTable(i).PatternTableData(lngVRAMIndex) = mabytFileData(lngROMIndex) 'F2 graphics are NOT compressed
                    lngVRAMIndex = lngVRAMIndex + 1
                    lngROMIndex = lngROMIndex + 1
                Next j
            End If
        Next i
    End Sub

    'Updates the user interface with values from the temporary internal memory arrays
    Private Sub UpdatePalette()
        'BG and Sprite
        For i As Integer = 0 To 15
            grpPalette.Controls("picBG" & Hex(i).PadLeft(2, "0")).BackColor = NEStoRGB(maPaletteData(gintLevel).PaletteData(i))
            grpPalette.Controls("picSprite" & Hex(i).PadLeft(2, "0")).BackColor = NEStoRGB(maPaletteData(gintLevel).PaletteData(i + &H10))
        Next i

        'Helipad Lights Flashing
        For i As Integer = 0 To 47
            grpPalette.Controls("picHeli" & Hex(i).PadLeft(2, "0")).BackColor = NEStoRGB(maHelipadPaletteData(gintLevel).PaletteData(i))
        Next i

        cmbHelipadScreenStart.SelectedIndex = maHelipadPaletteData(gintLevel).StartScreen
        cmbHelipadScreenEnd.SelectedIndex = maHelipadPaletteData(gintLevel).EndScreen

        'F2 BG
        If gacintLevelF2BGpaletteAddress(gintLevel) <> 0 Then
            For i As Integer = 0 To 15
                grpPalette.Controls("picF2BG" & Hex(i).PadLeft(2, "0")).Visible = True
                grpPalette.Controls("picF2BG" & Hex(i).PadLeft(2, "0")).BackColor = NEStoRGB(maF2PaletteData(gintLevel).BGPaletteData(i))
            Next i
        Else
            For i As Integer = 0 To 15
                grpPalette.Controls("picF2BG" & Hex(i).PadLeft(2, "0")).Visible = False
            Next i
        End If

        'F2 Sprite
        If gacintLevelF2SpritePaletteAddress(gintLevel) <> 0 Then
            For i As Integer = 0 To 15
                grpPalette.Controls("picF2Sprite" & Hex(i).PadLeft(2, "0")).Visible = True
                grpPalette.Controls("picF2Sprite" & Hex(i).PadLeft(2, "0")).BackColor = NEStoRGB(maF2PaletteData(gintLevel).SpritePaletteData(i))
            Next i
        Else
            For i As Integer = 0 To 15
                grpPalette.Controls("picF2Sprite" & Hex(i).PadLeft(2, "0")).Visible = False
            Next i
        End If
    End Sub

    Private Sub BuildLevelLargeTiles()
        Dim bmp As Bitmap = New Bitmap(&H100, &H260) 'Initialize the bitmap to full size (8x16 large tiles)
        Dim lngIndex As Long = 0
        Dim bytColorValue As Byte
        Dim intX As Integer
        Dim intY As Integer
        Dim intPatternTableOffset As Integer
        Dim Byte1 As Byte
        Dim Byte2 As Byte
        Dim lngLevelIndex As Long
        Dim intLevelPatternTableOffset As Integer
        Dim bytPalette As Byte

        'Lowest level (bit by bit color selection for each pixel)
        'data from this address: gacintLevelPatternTableAddress()
        'is read into this array: maPatternTable

        'definitions for 32x32 tiles
        'data from this address: gacintLevelLargeTileDataHeaderAddress()
        'is read into this array: maLevelData() 

        For i As Integer = 0 To gacintLevelTileCount(gintLevel)
            For j As Integer = 0 To 15 '16 small tiles per large tile
                For k = 0 To 7 '8 rows per small tile

                    If mabytFileData(gacintLevelLargeTileOffsetAddress(gintLevel)) <> 0 Then 'Level 2, 4, or 5.,  These levels use some of Level 1's tiles
                        If i < mabytFileData(gacintLevelLargeTileOffsetAddress(gintLevel)) Then
                            lngLevelIndex = 0
                            intLevelPatternTableOffset = 0
                        Else
                            lngLevelIndex = gintLevel
                            intLevelPatternTableOffset = mabytFileData(gacintLevelLargeTileOffsetAddress(gintLevel)) * &H10
                        End If
                    Else    'Level 1, 3, or 6  All these levels have all indpendent tile sets
                        lngLevelIndex = gintLevel
                        intLevelPatternTableOffset = 0
                    End If

                    For m = 0 To 7  '8 pixels per row (16 bytes total)
                        intX = (i Mod 8) * 32 + (j Mod 4) * 8 + m
                        intY = Int(i / 8) * 32 + Int((15 - j) / 4) * 8 + k
                        intPatternTableOffset = mabytFileData(gacintLevelLargeTileDefinitionAddress(lngLevelIndex) + lngIndex - intLevelPatternTableOffset) * &H10

                        If mabolF2Tiles(gintLevel, i) Then
                            Byte1 = (maF2PatternTable(lngLevelIndex).PatternTableData(intPatternTableOffset + k) And 2 ^ (7 - m))
                            Byte2 = (maF2PatternTable(lngLevelIndex).PatternTableData(intPatternTableOffset + k + 8) And 2 ^ (7 - m))

                        Else
                            Byte1 = (maPatternTable(lngLevelIndex).PatternTableData(intPatternTableOffset + k) And 2 ^ (7 - m))
                            Byte2 = (maPatternTable(lngLevelIndex).PatternTableData(intPatternTableOffset + k + 8) And 2 ^ (7 - m))
                        End If

                        bytColorValue = IIf(Byte1 = 0, 0, 1) + IIf(Byte2 = 0, 0, 2)

                        Select Case j
                            Case 2, 3, 6, 7
                                bytPalette = (mabytFileData(gacintLevelLargeTilePaletteAddress(gintLevel) + i) And &HC0) / 2 ^ 6
                            Case 0, 1, 4, 5
                                bytPalette = (mabytFileData(gacintLevelLargeTilePaletteAddress(gintLevel) + i) And &H30) / 2 ^ 4
                            Case 10, 11, 14, 15
                                bytPalette = (mabytFileData(gacintLevelLargeTilePaletteAddress(gintLevel) + i) And &HC) / 2 ^ 2
                            Case 8, 9, 12, 13
                                bytPalette = mabytFileData(gacintLevelLargeTilePaletteAddress(gintLevel) + i) And &H3
                        End Select

                        bmp.SetPixel(intX, intY, NEStoRGB(maPaletteData(gintLevel).PaletteData(bytColorValue + bytPalette * 4)))
                    Next m
                Next k
                lngIndex = lngIndex + 1
            Next j
        Next i

        picTile.Image = bmp
        picTile.Height = picTile.Image.Height
        picTile.Refresh()
    End Sub

    Private Sub BuildObjectImages()
        Dim bmp As Bitmap = New Bitmap(&H100, &H260) 'Initialize the bitmap to full size (8x16 large tiles)
        Dim lngIndex As Long = 0
        Dim bytColorValue As Byte
        Dim intX As Integer
        Dim intY As Integer
        Dim intPatternTableOffset As Integer
        Dim Byte1 As Byte
        Dim Byte2 As Byte
        Dim lngLevelIndex As Long
        Dim intLevelPatternTableOffset As Integer
        Dim bytPalette As Byte

        'Lowest level (bit by bit color selection for each pixel)
        'data from this address: gacintLevelPatternTableAddress()
        'is read into this array: maPatternTable

        'definitions for 32x32 tiles
        'data from this address: gacintLevelLargeTileDataHeaderAddress()
        'is read into this array: maLevelData() 

        For i As Integer = 0 To gacintLevelTileCount(gintLevel)
            For j As Integer = 0 To 15 '16 small tiles per large tile
                For k = 0 To 7 '8 rows per small tile

                    If mabytFileData(gacintLevelLargeTileOffsetAddress(gintLevel)) <> 0 Then 'Level 2, 4, or 5.,  These levels use some of Level 1's tiles
                        If i < mabytFileData(gacintLevelLargeTileOffsetAddress(gintLevel)) Then
                            lngLevelIndex = 0
                            intLevelPatternTableOffset = 0
                        Else
                            lngLevelIndex = gintLevel
                            intLevelPatternTableOffset = mabytFileData(gacintLevelLargeTileOffsetAddress(gintLevel)) * &H10
                        End If
                    Else    'Level 1, 3, or 6  All these levels have all indpendent tile sets
                        lngLevelIndex = gintLevel
                        intLevelPatternTableOffset = 0
                    End If

                    For m = 0 To 7  '8 pixels per row (16 bytes total)
                        intX = (i Mod 8) * 32 + (j Mod 4) * 8 + m
                        intY = Int(i / 8) * 32 + Int((15 - j) / 4) * 8 + k
                        intPatternTableOffset = mabytFileData(gacintLevelLargeTileDefinitionAddress(lngLevelIndex) + lngIndex - intLevelPatternTableOffset) * &H10
                        Byte1 = (maPatternTable(lngLevelIndex).PatternTableData(intPatternTableOffset + k) And 2 ^ (7 - m))
                        Byte2 = (maPatternTable(lngLevelIndex).PatternTableData(intPatternTableOffset + k + 8) And 2 ^ (7 - m))
                        bytColorValue = IIf(Byte1 = 0, 0, 1) + IIf(Byte2 = 0, 0, 2)

                        Select Case j
                            Case 2, 3, 6, 7
                                bytPalette = (mabytFileData(gacintLevelLargeTilePaletteAddress(gintLevel) + i) And &HC0) / 2 ^ 6
                            Case 0, 1, 4, 5
                                bytPalette = (mabytFileData(gacintLevelLargeTilePaletteAddress(gintLevel) + i) And &H30) / 2 ^ 4
                            Case 10, 11, 14, 15
                                bytPalette = (mabytFileData(gacintLevelLargeTilePaletteAddress(gintLevel) + i) And &HC) / 2 ^ 2
                            Case 8, 9, 12, 13
                                bytPalette = mabytFileData(gacintLevelLargeTilePaletteAddress(gintLevel) + i) And &H3
                        End Select

                        bmp.SetPixel(intX, intY, NEStoRGB(maPaletteData(gintLevel).PaletteData(bytColorValue + bytPalette * 4)))
                    Next m
                Next k
                lngIndex = lngIndex + 1
            Next j
        Next i

        picTile.Image = bmp
        picTile.Height = picTile.Image.Height
        picTile.Refresh()
    End Sub

    Private Sub SortAndPackObjectAndScreenAddressDatatoROM()
        Dim aNewObjects() As LevelObjectStruct
        Dim intMinYIndex As Integer
        Dim intMinYValue As Integer
        Dim abolUsedIndices() As Boolean
        Dim intFileIndex As Integer
        Dim intYOffset As Integer
        Dim intIndexOffset As Integer

        ReDim aNewObjects(UBound(gaObjects))

        'Create new temporary array class
        For j As Integer = 0 To 5
            For i As Integer = 0 To UBound(gaObjects(j).LevelObjects)
                ReDim Preserve aNewObjects(j).LevelObjects(i)
            Next i
        Next j

        '****
        'SORT
        '****
        For k As Integer = 0 To 5
            ReDim abolUsedIndices(UBound(gaObjects(k).LevelObjects))
            intYOffset = 0
            intIndexOffset = 0

            For i As Integer = 0 To UBound(gaObjects(k).LevelObjects)
                intMinYValue = Integer.MaxValue

                For j As Integer = 0 To UBound(gaObjects(k).LevelObjects)
                    If abolUsedIndices(j) = False Then
                        If gaObjects(k).LevelObjects(j).Y < intMinYValue Then
                            intMinYIndex = j
                            intMinYValue = gaObjects(k).LevelObjects(j).Y
                        End If
                    End If
                Next j

                If intMinYValue <> Integer.MaxValue Then
                    abolUsedIndices(intMinYIndex) = True
                    aNewObjects(k).LevelObjects(i).X = gaObjects(k).LevelObjects(intMinYIndex).X
                    aNewObjects(k).LevelObjects(i).Y = gaObjects(k).LevelObjects(intMinYIndex).Y
                    aNewObjects(k).LevelObjects(i).ObjectValue = gaObjects(k).LevelObjects(intMinYIndex).ObjectValue
                    aNewObjects(k).LevelObjects(i).SpawnAtBottom = gaObjects(k).LevelObjects(intMinYIndex).SpawnAtBottom
                Else
                    Stop '!!!!
                End If
            Next i

            For i As Integer = 0 To UBound(gaObjects(k).LevelObjects)
                gaObjects(k).LevelObjects(i).X = aNewObjects(k).LevelObjects(i).X
                gaObjects(k).LevelObjects(i).Y = aNewObjects(k).LevelObjects(i).Y
                gaObjects(k).LevelObjects(i).ObjectValue = aNewObjects(k).LevelObjects(i).ObjectValue
                gaObjects(k).LevelObjects(i).SpawnAtBottom = aNewObjects(k).LevelObjects(i).SpawnAtBottom
            Next i

            '****
            'PACK
            '****

            intFileIndex = gacintLevelObjectDataHeaderAddress(k)

            For i As Integer = 0 To UBound(gaObjects(k).LevelObjects)
                Do While (gaObjects(k).LevelObjects(i).Y - intYOffset) > &HFF
                    intYOffset = intYOffset + &H100

                    If Not (k = 0 And i = 0) Then    'The very first screen on Level 1 has no enemy spawns
                        mabytFileData(intFileIndex) = &HEF  'Mark the end of the screen section
                        intFileIndex += 1

                        maintScreenHeaderAddressData(k, Int(intYOffset / &H100)) = maintScreenHeaderAddressData(k, Int(intYOffset / &H100) - 1) + intIndexOffset + 1
                        mabytFileData(gacintLevelScreenHeaderStartAddress(k) + Int(intYOffset / &H100) * 2) = maintScreenHeaderAddressData(k, Int(intYOffset / &H100)) And &HFF
                        mabytFileData(gacintLevelScreenHeaderStartAddress(k) + Int(intYOffset / &H100) * 2 + 1) = CByte((maintScreenHeaderAddressData(k, Int(intYOffset / &H100)) And &HFF00) / &H100)
                        'Debug.Print(Hex(mabytFileData(gacintLevelScreenHeaderStartAddress(k) + Int(intYOffset / &H100) * 2 + 1)) & Hex(mabytFileData(gacintLevelScreenHeaderStartAddress(k) + Int(intYOffset / &H100) * 2)).ToString.PadLeft(2, "0"))

                        intIndexOffset = 0
                    End If

                    If k = 0 And i <> 0 Then 'Point the level 1 first screen header address to the first occurrence of &HEF to force no spawns and cycle to the next screen
                        'This will execute for every screen transition, but no matter, EF is EF
                        mabytFileData(gacintLevelScreenHeaderStartAddress(0)) = ((intFileIndex - &H1 - &H10) And &HFF)  '-1 since intfileindex has been incremented.  -&H10 because the .NES file has a 16 byte header
                        mabytFileData(gacintLevelScreenHeaderStartAddress(0) + 1) = ((intFileIndex - &H1 - &H10) And &HFF00) / &H100
                    End If
                Loop

                If isSpecialObject(gaObjects(k).LevelObjects(i).ObjectValue) Then
                    mabytFileData(intFileIndex) = gaObjects(k).LevelObjects(i).ObjectValue
                    mabytFileData(intFileIndex + 1) = gaObjects(k).LevelObjects(i).Y - intYOffset
                    intFileIndex += 2
                    intIndexOffset += 2
                Else
                    mabytFileData(intFileIndex) = gaObjects(k).LevelObjects(i).Y - intYOffset
                    mabytFileData(intFileIndex + 1) = gaObjects(k).LevelObjects(i).X + IIf(gaObjects(k).LevelObjects(i).SpawnAtBottom, &H80, 0)
                    mabytFileData(intFileIndex + 2) = gaObjects(k).LevelObjects(i).ObjectValue
                    intFileIndex += 3
                    intIndexOffset += 3
                End If
            Next i
        Next k
    End Sub

    Private Sub BuildScreenHeaderAddressData()
        For j As Integer = 0 To 5
            If j = 5 Then
                For i As Integer = 0 To 13
                    maintScreenHeaderAddressData(j, i) = mabytFileData(gacintLevelScreenHeaderStartAddress(j) + (i * 2)) + mabytFileData(gacintLevelScreenHeaderStartAddress(j) + (i * 2) + 1) * &H100
                Next i
            Else
                For i As Integer = 0 To 11
                    maintScreenHeaderAddressData(j, i) = mabytFileData(gacintLevelScreenHeaderStartAddress(j) + (i * 2)) + mabytFileData(gacintLevelScreenHeaderStartAddress(j) + (i * 2) + 1) * &H100
                Next i
            End If
        Next j
    End Sub

    'I've hacked the Jackal ROM to remove the last 12 bytes (of the total 16) for the helipad palette updates since they are the exact same as the
    'original palette and provide no value
    Private Sub ReadLevelPaletteData()
        For i As Integer = 0 To 5
            'BG and Sprite
            For j As Integer = gacintLevelPaletteAddress(i) To gacintLevelPaletteAddress(i) + &H1F
                maPaletteData(i).PaletteData(j - gacintLevelPaletteAddress(i)) = mabytFileData(j + 2)
            Next j

            'Helipad flashing lights
            If mabytFileData(&H1D714) = &HFE Then   'Hacked ROM
                For j As Integer = gacintLevelHelipadPaletteHACKEDAddress(i) To gacintLevelHelipadPaletteHACKEDAddress(i) + &H3
                    maHelipadPaletteData(i).PaletteData(j - gacintLevelHelipadPaletteHACKEDAddress(i)) = mabytFileData(j + 2)
                    maHelipadPaletteData(i).PaletteData(j - gacintLevelHelipadPaletteHACKEDAddress(i) + &H10) = mabytFileData(j + &H9)
                    maHelipadPaletteData(i).PaletteData(j - gacintLevelHelipadPaletteHACKEDAddress(i) + &H20) = mabytFileData(j + &H10)
                Next j
            Else    'Normal ROM
                For j As Integer = gacintLevelHelipadPaletteAddress(i) To gacintLevelHelipadPaletteAddress(i) + &HF
                    maHelipadPaletteData(i).PaletteData(j - gacintLevelHelipadPaletteAddress(i)) = mabytFileData(j + 2)
                    maHelipadPaletteData(i).PaletteData(j - gacintLevelHelipadPaletteAddress(i) + &H10) = mabytFileData(j + &H15)
                    maHelipadPaletteData(i).PaletteData(j - gacintLevelHelipadPaletteAddress(i) + &H20) = mabytFileData(j + &H28)
                Next j
            End If

            maHelipadPaletteData(i).StartScreen = mabytFileData(gacintLevelHelipadScreenAddress(i))
            maHelipadPaletteData(i).EndScreen = mabytFileData(gacintLevelHelipadScreenAddress(i) + 1)

            'F2 data
            For j As Integer = gacintLevelF2BGpaletteAddress(i) To gacintLevelF2BGpaletteAddress(i) + &HF
                maF2PaletteData(i).BGPaletteData(j - gacintLevelF2BGpaletteAddress(i)) = mabytFileData(j + 2)
            Next j

            For j As Integer = gacintLevelF2SpritePaletteAddress(i) To gacintLevelF2SpritePaletteAddress(i) + &HF
                maF2PaletteData(i).SpritePaletteData(j - gacintLevelF2SpritePaletteAddress(i)) = mabytFileData(j + 2)
            Next j
        Next i

        If mabytFileData(&H1D714) = &HFE Then   'Hacked ROM
            For i As Integer = 0 To &H2F    'Hide the unused helipad palette pictureboxes
                If i Mod 16 > 3 Then grpPalette.Controls("picHeli" & Hex(i).ToString.PadLeft(2, "0")).Hide()
            Next i
        End If
    End Sub

    Private Sub WritePaletteData()
        For i As Integer = 0 To 5
            'BG and Sprite
            For j As Integer = gacintLevelPaletteAddress(i) To gacintLevelPaletteAddress(i) + &H1F
                mabytFileData(j + 2) = maPaletteData(i).PaletteData(j - gacintLevelPaletteAddress(i))
            Next j

            'Helipad flashing lights
            If mabytFileData(&H1D714) = &HFE Then    'Hacked ROM
                For j As Integer = gacintLevelHelipadPaletteHACKEDAddress(i) To gacintLevelHelipadPaletteHACKEDAddress(i) + &H3
                    mabytFileData(j + 2) = maHelipadPaletteData(i).PaletteData(j - gacintLevelHelipadPaletteHACKEDAddress(i))
                    mabytFileData(j + &H9) = maHelipadPaletteData(i).PaletteData(j - gacintLevelHelipadPaletteHACKEDAddress(i) + &H10)
                    mabytFileData(j + &H10) = maHelipadPaletteData(i).PaletteData(j - gacintLevelHelipadPaletteHACKEDAddress(i) + &H20)
                Next j
            Else    'Normal ROM
                For j As Integer = gacintLevelHelipadPaletteAddress(i) To gacintLevelHelipadPaletteAddress(i) + &HF
                    mabytFileData(j + 2) = maHelipadPaletteData(i).PaletteData(j - gacintLevelHelipadPaletteAddress(i))
                    mabytFileData(j + &H15) = maHelipadPaletteData(i).PaletteData(j - gacintLevelHelipadPaletteAddress(i) + &H10)
                    mabytFileData(j + &H28) = maHelipadPaletteData(i).PaletteData(j - gacintLevelHelipadPaletteAddress(i) + &H20)
                Next j
            End If

            mabytFileData(gacintLevelHelipadScreenAddress(i)) = maHelipadPaletteData(i).StartScreen
            mabytFileData(gacintLevelHelipadScreenAddress(i) + 1) = maHelipadPaletteData(i).EndScreen

            'F2 data
            If gacintLevelF2BGpaletteAddress(i) <> 0 Then
                For j As Integer = gacintLevelF2BGpaletteAddress(i) To gacintLevelF2BGpaletteAddress(i) + &HF
                    mabytFileData(j + 2) = maF2PaletteData(i).BGPaletteData(j - gacintLevelF2BGpaletteAddress(i))
                Next j
            End If

            If gacintLevelF2SpritePaletteAddress(i) <> 0 Then
                For j As Integer = gacintLevelF2SpritePaletteAddress(i) To gacintLevelF2SpritePaletteAddress(i) + &HF
                    mabytFileData(j + 2) = maF2PaletteData(i).SpritePaletteData(j - gacintLevelF2SpritePaletteAddress(i))
                Next j
            End If
        Next i
    End Sub

    Private Sub OpenROM()
        Dim dlg As OpenFileDialog = New OpenFileDialog

        'Choose the NES file to edit
        dlg.Filter = "NES Files|*.NES"
        dlg.ShowDialog()
        If dlg.FileName <> "" Then
            Me.Text = "Jackal Level Editor   v:" & Application.ProductVersion & Space(20) & "ROM: " & dlg.FileName

            mstrROMFilePath = dlg.FileName
            Erase mabytFileData
            mabytFileData = GetAllFileData(mstrROMFilePath).ReturnData

            'Clear any data from a previously loaded ROM
            Erase gaObjects(0).LevelObjects
            Erase gaObjects(1).LevelObjects
            Erase gaObjects(2).LevelObjects
            Erase gaObjects(3).LevelObjects
            Erase gaObjects(4).LevelObjects
            Erase gaObjects(5).LevelObjects

            ReadLevelPaletteData()
            BuildPatternTables()
            BuildContiguousLevelData()
            BuildObjectData()
            BuildScreenHeaderAddressData()

            If Not maradLevel(0).Checked Then
                maradLevel(0).Checked = True 'Trigger loading Level 1
            Else
                radLevel_Changed(0)
            End If
        Else
            End
        End If
    End Sub

    Private Sub SaveROM()
        PackContiguousLevelDatatoROM()
        SortAndPackObjectAndScreenAddressDatatoROM()
        WritePaletteData()
        My.Computer.FileSystem.WriteAllBytes(mstrROMFilePath, mabytFileData, False) 'Write the updated ROM data back to the file

        'Reload the level and object data because objects were sorted
        LoadLevel()
        mrectLastObjectSelected = New Rectangle(New Point(0, 0), New Size(0, 0))
        LoadObjectsToLevel()

        'Clear data
        picSelected.Tag = ""
        picSelected.Image = Nothing
        grpSelection.Text = "Selection"

        mbolNotSaved = False    'Clear flag - ROM was just saved
    End Sub

    Private Sub SaveROMAs()
        Dim dlg As SaveFileDialog = New SaveFileDialog

        'Choose the NES file to edit
        dlg.Filter = "NES Files|*.NES"
        dlg.ShowDialog()
        If dlg.FileName <> "" Then
            PackContiguousLevelDatatoROM()
            SortAndPackObjectAndScreenAddressDatatoROM()
            WritePaletteData()
            My.Computer.FileSystem.WriteAllBytes(dlg.FileName, mabytFileData, False) 'Write the updated ROM to a new file

            'Reload the level and object data because objects were sorted
            LoadLevel()
            mrectLastObjectSelected = New Rectangle(New Point(0, 0), New Size(0, 0))
            LoadObjectsToLevel()

            'Clear data
            picSelected.Tag = ""
            picSelected.Image = Nothing
            grpSelection.Text = "Selection"

            mbolNotSaved = False    'Clear flag - ROM was just saved
        End If
    End Sub
#End Region

#Region "Radio Button"
    Private Sub radLevel_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles radLevel1.CheckedChanged, radLevel2.CheckedChanged, radLevel3.CheckedChanged, radLevel4.CheckedChanged, radLevel5.CheckedChanged, radLevel6.CheckedChanged
        Static aLastRadLevel(5) As Boolean
        Dim aRadLevel(5) As Boolean
        Dim bolNewChange As Boolean = False

        aRadLevel = {radLevel1.Checked, radLevel2.Checked, radLevel3.Checked, radLevel4.Checked, radLevel5.Checked, radLevel6.Checked}
        For i As Integer = 0 To UBound(aRadLevel)
            If aRadLevel(i) <> aLastRadLevel(i) Then
                bolNewChange = True
                Exit For
            End If
        Next i

        If bolNewChange Then
            For i As Integer = 0 To 5   'See which radio button was selected
                If maradLevel(i).Checked Then
                    radLevel_Changed(i)
                    Exit For
                End If
            Next i
        End If

        aLastRadLevel = {radLevel1.Checked, radLevel2.Checked, radLevel3.Checked, radLevel4.Checked, radLevel5.Checked, radLevel6.Checked}
    End Sub

    Private Sub radLevel_Changed(ByVal pintLevel As Integer)
        gintLevel = pintLevel   'Update the global level selection

        'Reset these variables because each level has specific tiles and enemies
        picSelected.Tag = ""
        picSelected.Image = Nothing
        grpSelection.Text = "Selection"

        'Set the level size
        Select Case pintLevel
            Case 0, 1, 2, 3, 4
                picLevel.Image = New Bitmap(16 * 32, 96 * 32)
            Case 5  'Level 6 has 2 additional screens
                picLevel.Image = New Bitmap(16 * 32, 112 * 32)
        End Select

        picLevel.Height = picLevel.Image.Height
        picTile.Image = New Bitmap(256, 512)
        picObject.Image = New Bitmap(8 * 32, CInt(((UBound(gaObjects(gintLevel).LevelObjects) / 8) + 1) * 32))
        picObject.Height = picObject.Image.Height
        picObject.Width = picObject.Image.Width

        mgfxLevel = Graphics.FromImage(picLevel.Image)
        mgfxObject = Graphics.FromImage(picObject.Image)

        'Load the new level
        UpdatePalette()
        BuildLevelLargeTiles()
        LoadLevel()
        mrectLastObjectSelected = New Rectangle(New Point(0, 0), New Size(0, 0))
        LoadObjectsToLevel()
    End Sub
#End Region

#Region "Palette"
    'BG
    Private Sub picBG_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles picBG00.Click, picBG01.Click, picBG02.Click, picBG03.Click, picBG04.Click, picBG05.Click, picBG06.Click, picBG07.Click, picBG08.Click, picBG09.Click, picBG0A.Click, picBG0B.Click, picBG0C.Click, picBG0D.Click, picBG0E.Click, picBG0F.Click
        picPalette(CType(sender, PictureBox), "BG")
    End Sub

    'Sprite
    Private Sub picSprite_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles picSprite00.Click, picSprite01.Click, picSprite02.Click, picSprite03.Click, picSprite04.Click, picSprite05.Click, picSprite06.Click, picSprite07.Click, picSprite08.Click, picSprite09.Click, picSprite0A.Click, picSprite0B.Click, picSprite0C.Click, picSprite0D.Click, picSprite0E.Click, picSprite0F.Click
        picPalette(CType(sender, PictureBox), "Sprite")
    End Sub

    'Helipad Lights Flashing
    Private Sub picHeli_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles picHeli00.Click, picHeli01.Click, picHeli02.Click, picHeli03.Click, picHeli04.Click, picHeli05.Click, picHeli06.Click, picHeli07.Click, picHeli08.Click, picHeli09.Click, picHeli0A.Click, picHeli0B.Click, picHeli0C.Click, picHeli0D.Click, picHeli0E.Click, picHeli0F.Click, picHeli10.Click, picHeli11.Click, picHeli12.Click, picHeli13.Click, picHeli14.Click, picHeli15.Click, picHeli16.Click, picHeli17.Click, picHeli18.Click, picHeli19.Click, picHeli1A.Click, picHeli1B.Click, picHeli1C.Click, picHeli1D.Click, picHeli1E.Click, picHeli1F.Click, picHeli20.Click, picHeli21.Click, picHeli22.Click, picHeli23.Click, picHeli24.Click, picHeli25.Click, picHeli26.Click, picHeli27.Click, picHeli28.Click, picHeli29.Click, picHeli2A.Click, picHeli2B.Click, picHeli2C.Click, picHeli2D.Click, picHeli2E.Click, picHeli2F.Click
        picPalette(CType(sender, PictureBox), "Heli")
    End Sub

    'F2 BG
    Private Sub picF2BG_Click(sender As Object, e As EventArgs) Handles picF2BG00.Click, picF2BG01.Click, picF2BG02.Click, picF2BG03.Click, picF2BG04.Click, picF2BG05.Click, picF2BG06.Click, picF2BG07.Click, picF2BG08.Click, picF2BG09.Click, picF2BG0A.Click, picF2BG0B.Click, picF2BG0C.Click, picF2BG0D.Click, picF2BG0E.Click, picF2BG0F.Click
        picPalette(CType(sender, PictureBox), "F2BG")
    End Sub

    'F2 Sprite
    Private Sub picF2Sprite00_Click(sender As Object, e As EventArgs) Handles picF2Sprite00.Click, picF2Sprite01.Click, picF2Sprite02.Click, picF2Sprite03.Click, picF2Sprite04.Click, picF2Sprite05.Click, picF2Sprite06.Click, picF2Sprite07.Click, picF2Sprite08.Click, picF2Sprite09.Click, picF2Sprite0A.Click, picF2Sprite0B.Click, picF2Sprite0C.Click, picF2Sprite0D.Click, picF2Sprite0E.Click, picF2Sprite0F.Click
        picPalette(CType(sender, PictureBox), "F2Sprite")
    End Sub

    ''' <summary>
    ''' Handles the click event for any palette picturebox
    ''' </summary>
    ''' <param name="pctl">the picturebox control that generated the event</param>
    ''' <param name="pstrType">The palette type.  Background, Sprite, Helipad, or F2</param>
    ''' <remarks></remarks>
    Private Sub picPalette(ByVal pctl As PictureBox, ByVal pstrType As String)
        Dim intIndex As Integer = "&H" & VB.Right(pctl.Name, 2) 'Every control is names 'picNAME##', where ## is the index number
        Dim form As formNESColor = New formNESColor

        form.FormLeft = Me.Left + Me.Width / 2
        form.ShowDialog()

        mbolNotSaved = True 'Something changed.  set the need to save flag

        If form.bytNESColor < &H40 Then
            pctl.BackColor = NEStoRGB(form.bytNESColor)

            If pstrType = "BG" Then
                maPaletteData(gintLevel).PaletteData(intIndex) = form.bytNESColor
            ElseIf pstrType = "Sprite" Then
                maPaletteData(gintLevel).PaletteData(intIndex + &H10) = form.bytNESColor
            ElseIf pstrType = "Heli" Then
                maHelipadPaletteData(gintLevel).PaletteData(intIndex) = form.bytNESColor
            ElseIf pstrType = "F2BG" Then
                maF2PaletteData(gintLevel).BGPaletteData(intIndex) = form.bytNESColor
            ElseIf pstrType = "F2Sprite" Then
                maF2PaletteData(gintLevel).SpritePaletteData(intIndex) = form.bytNESColor
            Else
                'Should never get here
            End If

            radLevel_Changed(gintLevel) 'Trigger a reload of the level with the new palettes
        End If
    End Sub

    Private Sub cmbHelipadScreenStart_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmbHelipadScreenStart.SelectedIndexChanged
        maHelipadPaletteData(gintLevel).StartScreen = "&H" & Trim(cmbHelipadScreenStart.Text)
    End Sub

    Private Sub cmbHelipadScreenEnd_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmbHelipadScreenEnd.SelectedIndexChanged
        maHelipadPaletteData(gintLevel).EndScreen = "&H" & Trim(cmbHelipadScreenEnd.Text)
    End Sub

    'Resets the Selection groupbox to the default state
    Private Sub ResetObjectDisplayXY()
        lblObjectX.Text = 0
        lblObjectY.Text = 0
        lblOddY.Visible = False
        lblSpawnsAtBottom.Visible = False
    End Sub
#End Region

#Region "Utility"
    Private Sub Initialize()
        Me.Text = "Jackal Level Editor   v:" & Application.ProductVersion

        'center the form on the screen
        Me.Top = (Screen.PrimaryScreen.Bounds.Height - Me.Height) / 2
        Me.Left = (Screen.PrimaryScreen.Bounds.Width - Me.Width) / 2

        'Initialization.  This is necessary to handle different resolutions/OSs/etc
        Me.Width = 1435
        Me.Height = 900

        tlpMain.ColumnStyles(0).Width = 540
        tlpMain.ColumnStyles(2).Width = 710

        picLevel.Width = 512
        picLevel.Height = 3072

        picTile.Width = 256
        picTile.Height = 603
        picObject.Width = 256
        picObject.Height = 603

        picSelected.Width = 64
        picSelected.Height = 64

        'Create Level Radio Button array
        ReDim maradLevel(5)
        maradLevel(0) = radLevel1
        maradLevel(1) = radLevel2
        maradLevel(2) = radLevel3
        maradLevel(3) = radLevel4
        maradLevel(4) = radLevel5
        maradLevel(5) = radLevel6

        ReDim gaObjects(5)
        ReDim gaObjects(0).ConversionTable(&HFF)    'All stages have less than &HFF total objects
        ReDim gaObjects(1).ConversionTable(&HFF)
        ReDim gaObjects(2).ConversionTable(&HFF)
        ReDim gaObjects(3).ConversionTable(&HFF)
        ReDim gaObjects(4).ConversionTable(&HFF)
        ReDim gaObjects(5).ConversionTable(&HFF)
        ReDim gaObjects(0).Offset(&HFF)
        ReDim gaObjects(1).Offset(&HFF)
        ReDim gaObjects(2).Offset(&HFF)
        ReDim gaObjects(3).Offset(&HFF)
        ReDim gaObjects(4).Offset(&HFF)
        ReDim gaObjects(5).Offset(&HFF)

        BuildObjectConversionTables()

        ReDim maLevelData(5)
        ReDim maLevelData(0).LevelData((96 * 16) - 1)    'Level 1 has 12 screens
        ReDim maLevelData(1).LevelData((96 * 16) - 1)    'Level 2 has 12 screens
        ReDim maLevelData(2).LevelData((96 * 16) - 1)    'Level 3 has 12 screens
        ReDim maLevelData(3).LevelData((96 * 16) - 1)    'Level 4 has 12 screens
        ReDim maLevelData(4).LevelData((96 * 16) - 1)    'Level 5 has 12 screens
        ReDim maLevelData(5).LevelData((112 * 16) - 1)   'Level 6 has an additional 2 screens of data

        ReDim maintScreenHeaderAddressData(5, 13) 'all stages are 12 screens tall (level 6 has 14).  final boss screen is not editable at the moment

        ReDim maPatternTable(5)
        ReDim maPatternTable(0).PatternTableData(&H1FFF)    'BG pattern table is 0000-FFFF.  Sprite pattern table is 1000-1FFF
        ReDim maPatternTable(1).PatternTableData(&H1FFF)
        ReDim maPatternTable(2).PatternTableData(&H1FFF)
        ReDim maPatternTable(3).PatternTableData(&H1FFF)
        ReDim maPatternTable(4).PatternTableData(&H1FFF)
        ReDim maPatternTable(5).PatternTableData(&H1FFF)

        ReDim maF2PatternTable(5)
        ReDim maF2PatternTable(0).PatternTableData(&H1FFF)    'BG pattern table is 0000-FFFF.  Sprite pattern table is 1000-1FFF
        ReDim maF2PatternTable(1).PatternTableData(&H1FFF)
        ReDim maF2PatternTable(2).PatternTableData(&H1FFF)
        ReDim maF2PatternTable(3).PatternTableData(&H1FFF)
        ReDim maF2PatternTable(4).PatternTableData(&H1FFF)
        ReDim maF2PatternTable(5).PatternTableData(&H1FFF)

        ReDim mabolF2Tiles(5, &HFF) '6 Levels, all levels have less then &HFF tiles.  just use &HFF
        BuildF2TileArray()

        'These are the main palettes (6 levels) for the BG and sprites, so 32 bytes each
        ReDim maPaletteData(5)
        ReDim maPaletteData(0).PaletteData(31)
        ReDim maPaletteData(1).PaletteData(31)
        ReDim maPaletteData(2).PaletteData(31)
        ReDim maPaletteData(3).PaletteData(31)
        ReDim maPaletteData(4).PaletteData(31)
        ReDim maPaletteData(5).PaletteData(31)

        'Each helipad (6 levels) has 3 BG palettes that it cycles through (16 bytes each)
        ReDim maHelipadPaletteData(5)
        ReDim maHelipadPaletteData(0).PaletteData(47)
        ReDim maHelipadPaletteData(1).PaletteData(47)
        ReDim maHelipadPaletteData(2).PaletteData(47)
        ReDim maHelipadPaletteData(3).PaletteData(47)
        ReDim maHelipadPaletteData(4).PaletteData(47)
        ReDim maHelipadPaletteData(5).PaletteData(47)

        'Track F2 palette changes
        ReDim maF2PaletteData(5)

        ReDim maF2PaletteData(0).BGPaletteData(&HF)
        ReDim maF2PaletteData(0).SpritePaletteData(&HF)
        ReDim maF2PaletteData(1).BGPaletteData(&HF)
        ReDim maF2PaletteData(1).SpritePaletteData(&HF)
        ReDim maF2PaletteData(2).BGPaletteData(&HF)
        ReDim maF2PaletteData(2).SpritePaletteData(&HF)
        ReDim maF2PaletteData(3).BGPaletteData(&HF)
        ReDim maF2PaletteData(3).SpritePaletteData(&HF)
        ReDim maF2PaletteData(4).BGPaletteData(&HF)
        ReDim maF2PaletteData(4).SpritePaletteData(&HF)
        ReDim maF2PaletteData(5).BGPaletteData(&HF)
        ReDim maF2PaletteData(5).SpritePaletteData(&HF)

        'Screens are typically 0 to B (D for level 6).  E & F are included if the user never wants the helipad palettes to flash
        For i As Integer = 0 To &HF
            'Padding is added for simple esthetics
            cmbHelipadScreenStart.Items.Add(Hex(i).ToString.PadLeft(4, " "))
            cmbHelipadScreenEnd.Items.Add(Hex(i).ToString.PadLeft(4, " "))
        Next i
    End Sub

    Private Sub LoadLevel()
        Dim intIndexDestX As Integer
        Dim intIndexDestY As Integer
        Dim intIndexSourceX As Integer
        Dim intIndexSourceY As Integer
        Dim bmpLargeTile As Bitmap

        For i As Integer = 0 To UBound(maLevelData(gintLevel).LevelData)
            intIndexSourceX = maLevelData(gintLevel).LevelData(i) Mod 8
            intIndexSourceY = Int(maLevelData(gintLevel).LevelData(i) / 8)

            bmpLargeTile = ImageSubset(picTile.Image, New Rectangle(New Point(0, 0), New Point(32, 32)), New Rectangle(intIndexSourceX * 32, intIndexSourceY * 32, 32, 32))
            'If intIndexDestY >= maHelipadPaletteData(gintLevel).StartScreen And intIndexDestY <= maHelipadPaletteData(gintLevel).EndScreen Then
            '!!!!find out what palette was used, then swap the colors for the helipad palette
            'End If
            mgfxLevel.DrawImage(bmpLargeTile, New Point(intIndexDestX * 32, picLevel.Height - (intIndexDestY * 32) - 32))

            intIndexDestX = intIndexDestX + 1
            If intIndexDestX = 16 Then
                intIndexDestX = 0
                intIndexDestY = intIndexDestY + 1
            End If
        Next i

        ResetObjectDisplayXY()

        DrawScreenLines()
        If chkGridLines.Checked Then DrawScreenGrid()
        picLevel.Refresh()
    End Sub

    'Load a 3x3 area of the level (saves time if only this part of the level was modified)
    Private Sub LoadLevel(ByVal pintIndex As Integer)
        Dim intIndexDestX As Integer
        Dim intIndexDestY As Integer
        Dim intIndexSourceX As Integer
        Dim intIndexSourceY As Integer
        Dim aintIndex(8) As Integer

        'Build a 3x3 set of tiles with the updated tile in the middle (each tile here is 32px by 32px)
        'These are the tiles to refresh.  we need to update this many because the affect may be larger than one tile
        aintIndex(0) = pintIndex - 17
        aintIndex(1) = pintIndex - 16
        aintIndex(2) = pintIndex - 15
        aintIndex(3) = pintIndex - 1
        aintIndex(4) = pintIndex
        aintIndex(5) = pintIndex + 1
        aintIndex(6) = pintIndex + 15
        aintIndex(7) = pintIndex + 16
        aintIndex(8) = pintIndex + 17

        For i As Integer = 0 To UBound(aintIndex)   'Process each tile position
            If aintIndex(i) >= 0 And aintIndex(i) < IIf(gintLevel = 5, &H700, &H600) Then   'Verify the location is inside the level
                intIndexSourceX = maLevelData(gintLevel).LevelData(aintIndex(i)) Mod 8
                intIndexSourceY = Int(maLevelData(gintLevel).LevelData(aintIndex(i)) / 8)
                intIndexDestX = aintIndex(i) Mod 16
                intIndexDestY = Int(aintIndex(i) / 16)

                'Update the tile
                mgfxLevel.DrawImage(ImageSubset(picTile.Image, New Rectangle(New Point(0, 0), New Point(32, 32)), New Rectangle(intIndexSourceX * 32, intIndexSourceY * 32, 32, 32)), New Point(intIndexDestX * 32, picLevel.Height - (intIndexDestY * 32) - 32))
            End If
        Next i

        DrawScreenLines()
        If chkGridLines.Checked Then DrawScreenGrid()
        picLevel.Refresh()
    End Sub

    Private Sub LoadObjectsToLevel()
        Dim intIndexDestX As Integer
        Dim intIndexDestY As Integer = 0
        Dim intIndexSourceY As Integer
        Dim bmpSource As Bitmap
        Dim bmpObject As Bitmap

        'Load the correct level's object list image
        Select Case gintLevel
            Case 0
                bmpSource = My.Resources.Objects_Level1
            Case 1
                bmpSource = My.Resources.Objects_Level2
            Case 2
                bmpSource = My.Resources.Objects_Level3
            Case 3
                bmpSource = My.Resources.Objects_Level4
            Case 4
                bmpSource = My.Resources.Objects_Level5
            Case Else   'Should only be 5
                bmpSource = My.Resources.Objects_Level6
        End Select

        With gaObjects(gintLevel)
            For i As Integer = 0 To UBound(.LevelObjects)   'Loop through all the objects in the level
                intIndexSourceY = 0

                'Find the next object in the level's object conversion table
                intIndexSourceY = FindObjectInConversionTable(.LevelObjects(i).ObjectValue)

                bmpObject = ImageSubset(bmpSource, New Rectangle(0, 0, 32, 32), New Rectangle(0, intIndexSourceY * 32, 32, 32)) 'New Rectangle(New Point(0, 0), New Point(32, 32)), New Rectangle(0, intIndexSourceY * 32, 32, 32))
                mgfxObject.DrawImage(New Bitmap(bmpObject), New Point(intIndexDestX * 32, intIndexDestY * 32))
                bmpObject.MakeTransparent(gcclrObjectBackground)
                mgfxLevel.DrawImage(New Bitmap(bmpObject), New Point(((.LevelObjects(i).X + .Offset(intIndexSourceY).X) * 4) - 16, picLevel.Height - (.LevelObjects(i).Y) - 32 + .Offset(intIndexSourceY).Y))

                intIndexDestX = intIndexDestX + 1
                If intIndexDestX = 8 Then   'Modular arithmetic
                    intIndexDestX = 0
                    intIndexDestY = intIndexDestY + 1
                End If
            Next i
        End With

        picObject.Refresh()
        picLevel.Refresh()

        If mrectLastObjectSelected <> New Rectangle(New Point(0, 0), New Size(0, 0)) Then
            mgfxObject.DrawRectangle(New Pen(New SolidBrush(Color.Red), 2), mrectLastObjectSelected)  'Draw the box around the object
            picObject.Refresh()
        End If
    End Sub

    Private Sub DrawScreenLines()
        Dim intScreens As Integer = IIf(gintLevel = 5, 14, 12)  'Level 6 has 14 screens instead of the usual 12

        For i As Integer = 1 To intScreens - 1
            mgfxLevel.DrawLine(New Pen(Brushes.Black), New Point(0, picLevel.Height - (256 * i)), New Point(512, picLevel.Height - (256 * i)))
            If chkGrayLines.Checked Then
                For j As Integer = 1 To 15
                    mgfxLevel.DrawLine(New Pen(New SolidBrush(Color.FromArgb(255, 100, 100, 100))), New Point(0, picLevel.Height - (256 * i) - j), New Point(512, picLevel.Height - (256 * i) - j))
                Next j
            End If
            mgfxLevel.DrawLine(New Pen(Brushes.Black), New Point(0, picLevel.Height - 256 * i - 16), New Point(512, picLevel.Height - 256 * i - 16))
        Next i

        If chkGrayLines.Checked Then
            For j As Integer = 1 To 23
                mgfxLevel.DrawLine(New Pen(New SolidBrush(Color.FromArgb(255, 100, 100, 100))), New Point(0, picLevel.Height - j), New Point(512, picLevel.Height - j))
            Next j
        End If
        mgfxLevel.DrawLine(New Pen(Brushes.Black), New Point(0, picLevel.Height - 24), New Point(512, picLevel.Height - 24))
    End Sub

    Private Sub DrawScreenGrid()
        For i As Integer = 0 To picLevel.Height Step 32
            mgfxLevel.DrawLine(New Pen(New SolidBrush(Color.FromArgb(255, 0, 255, 0))), New Point(0, picLevel.Height - i), New Point(512, picLevel.Height - i))
        Next i

        For i As Integer = 0 To picLevel.Width Step 32
            mgfxLevel.DrawLine(New Pen(New SolidBrush(Color.FromArgb(255, 0, 255, 0))), New Point(picLevel.Width - i, 0), New Point(picLevel.Width - i, picLevel.Height))
        Next i
    End Sub

    Public Function isSpecialObject(pbytObject) As Boolean
        'F0 triggers the screen scroll for initiating the level's boss battle (hitting the actual boss load trigger cause the scrolling to stop)
        'F1 reloads the original level's BG and sprite palettes
        'F2 Sets the sprite palette data to something new (e.g. Level 1 boss updates sprite data, though there's no real color change)
        '   F2 also loads new graphics into the palette table (e.g. Level 6 holes in the ground made by the laser boss, Level 3 boss tiles)
        isSpecialObject = ((pbytObject = &HF0) Or (pbytObject = &HF1) Or (pbytObject = &HF2))
    End Function

    Public Function FindObjectInConversionTable(pintObjectValue As Integer) As Integer
        FindObjectInConversionTable = 0

        For j As Integer = 0 To UBound(gaObjects(gintLevel).ConversionTable)
            If gaObjects(gintLevel).ConversionTable(j).ObjectValue = pintObjectValue Then
                FindObjectInConversionTable = j
                Exit For
            End If
        Next j
    End Function

    ''' <summary>
    ''' Determines whether the new location for an object is valid
    ''' </summary>
    ''' <param name="pintX">Y coordinate</param>
    ''' <param name="pintY">X coordinate</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function isPlacementOK(pintX As Integer, pintY As Integer) As Boolean
        isPlacementOK = True    'Initialize

        'No objects may be placed on the 1st screen of Level 1
        If gintLevel = 0 Then
            If pintY < &H100 Then
                isPlacementOK = False
                'MsgBox("No Events may be placed on the First screen of the First Level.  This would corrupt the ROM", MsgBoxStyle.Critical, "Invalid Event Placement")
                Exit Function
            End If
        End If

        'No objects will spawn below this Y coord on the 1st screen of any level
        If pintY < &H10 Then
            isPlacementOK = False
            'MsgBox("No objects can be placed below Y coordinate 10 (Hex) on the first screen.  They will not spawn", MsgBoxStyle.Critical, "Invalid Event Placement")
            Exit Function
        End If

        'No objects can be placed between &HEF and &HFF, inclusive
        If (pintY Mod &H100 >= &HEF) And (pintY Mod &H100 <= &HFF) Then
            isPlacementOK = False
            'MsgBox("No objects can be placed between (inclusive) EF and FF (Hex) on any level, any screen.  This will CORRUPT the ROM", MsgBoxStyle.Critical, "Invalid Event Placement")
            Exit Function
        End If

        'Horizontal image bounds
        If (pintX * 4) < 0 Or (pintX * 4) > picLevel.Width Then
            isPlacementOK = False
            Exit Function
        End If

        'Vertical image bounds
        If pintY < 0 Or pintY >= picLevel.Height Then
            isPlacementOK = False
            Exit Function
        End If
    End Function

    Private Sub BuildObjectConversionTables()
        '!!!! Change these conversion tables to a single conversion table.
        'This relates how the images are oriented in My.Resources.Objects_Level# and the value Jackal assigns this object
        gaObjects(0).ConversionTable(0).ObjectValue = &H85
        gaObjects(0).ConversionTable(0).Name = "Platform Tank (White)"
        gaObjects(0).Offset(0) = New Point(0, 6)
        gaObjects(0).ConversionTable(1).ObjectValue = &H86
        gaObjects(0).ConversionTable(1).Name = "Platform Tank (Yellow)"
        gaObjects(0).Offset(1) = New Point(0, 6)
        gaObjects(0).ConversionTable(2).ObjectValue = &H1
        gaObjects(0).ConversionTable(2).Name = "Mobile Soldier"
        gaObjects(0).ConversionTable(3).ObjectValue = &H2
        gaObjects(0).ConversionTable(3).Name = "Stationary Soldier"
        gaObjects(0).ConversionTable(4).ObjectValue = &H95
        gaObjects(0).ConversionTable(4).Name = "POW Upgrade Barracks Entrance"
        gaObjects(0).Offset(4) = New Point(0, 8)
        gaObjects(0).ConversionTable(5).ObjectValue = &H9B
        gaObjects(0).ConversionTable(5).Name = "Gate"
        gaObjects(0).ConversionTable(6).ObjectValue = &H93
        gaObjects(0).ConversionTable(6).Name = "POW Right Entrance (2)"
        gaObjects(0).Offset(6) = New Point(4, 0)
        gaObjects(0).ConversionTable(7).ObjectValue = &H7
        gaObjects(0).ConversionTable(7).Name = "Red Tank"
        gaObjects(0).ConversionTable(8).ObjectValue = &H9D
        gaObjects(0).ConversionTable(8).Name = "POW Left Entrance (4)"
        gaObjects(0).Offset(8) = New Point(-4, 0)
        gaObjects(0).ConversionTable(9).ObjectValue = &H9C
        gaObjects(0).ConversionTable(9).Name = "POW Right Entrance (4)"
        gaObjects(0).Offset(9) = New Point(4, 0)
        gaObjects(0).ConversionTable(10).ObjectValue = &H8
        gaObjects(0).ConversionTable(10).Name = "Attack Boat"
        gaObjects(0).ConversionTable(11).ObjectValue = &HBD
        gaObjects(0).ConversionTable(11).Name = "Landed Helicopter (Loads From Right)"
        gaObjects(0).ConversionTable(12).ObjectValue = &HBE
        gaObjects(0).ConversionTable(12).Name = "Landed Helicopter (Loads From Left)"
        gaObjects(0).ConversionTable(13).ObjectValue = &HD0
        gaObjects(0).ConversionTable(13).Name = "Power Up-Kill Enemies On Screen"
        gaObjects(0).ConversionTable(14).ObjectValue = &HD1
        gaObjects(0).ConversionTable(14).Name = "Power Up-Full Bazooka"
        gaObjects(0).ConversionTable(15).ObjectValue = &HD2
        gaObjects(0).ConversionTable(15).Name = "Power Up-1Up"
        gaObjects(0).ConversionTable(16).ObjectValue = &HBC
        gaObjects(0).ConversionTable(16).Name = "Flying Helicopter (Going to helipad)"
        gaObjects(0).ConversionTable(17).ObjectValue = &HBF
        gaObjects(0).ConversionTable(17).Name = "Drop off for POWs"
        gaObjects(0).ConversionTable(18).ObjectValue = &HA
        gaObjects(0).ConversionTable(18).Name = "Level 1 Boss Tank"
        gaObjects(0).ConversionTable(19).ObjectValue = &HF0
        gaObjects(0).ConversionTable(19).Name = "Level End Screen Scroll"
        gaObjects(0).ConversionTable(20).ObjectValue = &HF1
        gaObjects(0).ConversionTable(20).Name = "Reload Original level Palette"
        gaObjects(0).ConversionTable(21).ObjectValue = &HF2
        gaObjects(0).ConversionTable(21).Name = "Change Palette"
        gaObjects(0).ConversionTable(22).ObjectValue = &H89
        gaObjects(0).ConversionTable(22).Name = "Level 1 Boss"

        gaObjects(1).ConversionTable(0).ObjectValue = &H85
        gaObjects(1).ConversionTable(0).Name = "Platform Tank (White)"
        gaObjects(1).Offset(0) = New Point(0, 6)
        gaObjects(1).ConversionTable(1).ObjectValue = &H86
        gaObjects(1).ConversionTable(1).Name = "Platform Tank (Yellow)"
        gaObjects(1).Offset(1) = New Point(0, 6)
        gaObjects(1).ConversionTable(2).ObjectValue = &H1
        gaObjects(1).ConversionTable(2).Name = "Mobile Soldier"
        gaObjects(1).ConversionTable(3).ObjectValue = &H2
        gaObjects(1).ConversionTable(3).Name = "Stationary Soldier"
        gaObjects(1).ConversionTable(4).ObjectValue = &H10
        gaObjects(1).ConversionTable(4).Name = "Booby Trap Pillar (Falls Left)"
        gaObjects(1).ConversionTable(5).ObjectValue = &H1F
        gaObjects(1).ConversionTable(5).Name = "Booby Trap Pillar (Falls Right)"
        gaObjects(1).ConversionTable(6).ObjectValue = &H93
        gaObjects(1).ConversionTable(6).Name = "POW Right Entrance (2)"
        gaObjects(1).Offset(6) = New Point(4, 0)
        gaObjects(1).ConversionTable(7).ObjectValue = &H7
        gaObjects(1).ConversionTable(7).Name = "Red Tank"
        gaObjects(1).ConversionTable(8).ObjectValue = &H9D
        gaObjects(1).ConversionTable(8).Name = "POW Left Entrance (4)"
        gaObjects(1).Offset(8) = New Point(-4, 0)
        gaObjects(1).ConversionTable(9).ObjectValue = &H9C
        gaObjects(1).ConversionTable(9).Name = "POW Right Entrance (4)"
        gaObjects(1).Offset(9) = New Point(4, 0)
        gaObjects(1).ConversionTable(10).ObjectValue = &HB
        gaObjects(1).ConversionTable(10).Name = "Silver Tank"
        gaObjects(1).ConversionTable(11).ObjectValue = &HBD
        gaObjects(1).ConversionTable(11).Name = "Landed Helicopter (Loads From Right)"
        gaObjects(1).ConversionTable(12).ObjectValue = &HBE
        gaObjects(1).ConversionTable(12).Name = "Landed Helicopter (Loads From Left)"
        gaObjects(1).ConversionTable(13).ObjectValue = &HBC
        gaObjects(1).ConversionTable(13).Name = "Flying Helicopter (Going to helipad)"
        gaObjects(1).ConversionTable(14).ObjectValue = &HBF
        gaObjects(1).ConversionTable(14).Name = "Drop off for POWs"
        gaObjects(1).ConversionTable(15).ObjectValue = &HF
        gaObjects(1).ConversionTable(15).Name = "Enemy Jeep"
        gaObjects(1).ConversionTable(16).ObjectValue = &H3A
        gaObjects(1).ConversionTable(16).Name = "Attack Plane - Specific"
        gaObjects(1).ConversionTable(17).ObjectValue = &H3B
        gaObjects(1).ConversionTable(17).Name = "Attack Plane - Anywhere"
        gaObjects(1).ConversionTable(18).ObjectValue = &H9B
        gaObjects(1).ConversionTable(18).Name = "Gate"
        gaObjects(1).ConversionTable(19).ObjectValue = &HA1
        gaObjects(1).ConversionTable(19).Name = "Head Statue (Always shots)"
        gaObjects(1).ConversionTable(20).ObjectValue = &HA2
        gaObjects(1).ConversionTable(20).Name = "Head Statue (Idle)"
        gaObjects(1).ConversionTable(21).ObjectValue = &HD0
        gaObjects(1).ConversionTable(21).Name = "Power Up-Kill Enemies On Screen"
        gaObjects(1).ConversionTable(22).ObjectValue = &HD1
        gaObjects(1).ConversionTable(22).Name = "Power Up-Full Bazooka"
        gaObjects(1).ConversionTable(23).ObjectValue = &HD2
        gaObjects(1).ConversionTable(23).Name = "Power Up-1Up"
        gaObjects(1).ConversionTable(24).ObjectValue = &H95
        gaObjects(1).ConversionTable(24).Name = "POW Upgrade Barracks Entrance"
        gaObjects(1).Offset(24) = New Point(0, 8)
        gaObjects(1).ConversionTable(25).ObjectValue = &HF0
        gaObjects(1).ConversionTable(25).Name = "Level End Screen Scroll"
        gaObjects(1).ConversionTable(26).ObjectValue = &HF1
        gaObjects(1).ConversionTable(26).Name = "Reload Original level Palette"
        gaObjects(1).ConversionTable(27).ObjectValue = &HF2
        gaObjects(1).ConversionTable(27).Name = "Change Palette"
        gaObjects(1).ConversionTable(28).ObjectValue = &HA0
        gaObjects(1).ConversionTable(28).Name = "Level 2 Boss"

        gaObjects(2).ConversionTable(0).ObjectValue = &H85
        gaObjects(2).ConversionTable(0).Name = "Platform Tank (White)"
        gaObjects(2).Offset(0) = New Point(0, 6)
        gaObjects(2).ConversionTable(1).ObjectValue = &H86
        gaObjects(2).ConversionTable(1).Name = "Platform Tank (Yellow)"
        gaObjects(2).Offset(1) = New Point(0, 6)
        gaObjects(2).ConversionTable(2).ObjectValue = &H1
        gaObjects(2).ConversionTable(2).Name = "Mobile Soldier"
        gaObjects(2).ConversionTable(3).ObjectValue = &H2
        gaObjects(2).ConversionTable(3).Name = "Stationary Soldier"
        gaObjects(2).ConversionTable(4).ObjectValue = &H1A
        gaObjects(2).ConversionTable(4).Name = "Large Attack Boat"
        gaObjects(2).ConversionTable(5).ObjectValue = &H11
        gaObjects(2).ConversionTable(5).Name = "Mobile Infantry Transport Truck"
        gaObjects(2).ConversionTable(6).ObjectValue = &H93
        gaObjects(2).ConversionTable(6).Name = "POW Right Entrance (2)"
        gaObjects(2).Offset(6) = New Point(4, 0)
        gaObjects(2).ConversionTable(7).ObjectValue = &H7
        gaObjects(2).ConversionTable(7).Name = "Red Tank"
        gaObjects(2).ConversionTable(8).ObjectValue = &H9D
        gaObjects(2).ConversionTable(8).Name = "POW Left Entrance (4)"
        gaObjects(2).Offset(8) = New Point(-4, 0)
        gaObjects(2).ConversionTable(9).ObjectValue = &H9C
        gaObjects(2).ConversionTable(9).Name = "POW Right Entrance (4)"
        gaObjects(2).Offset(9) = New Point(4, 0)
        gaObjects(2).ConversionTable(10).ObjectValue = &HB8
        gaObjects(2).ConversionTable(10).Name = "Laser"
        gaObjects(2).ConversionTable(11).ObjectValue = &HBD
        gaObjects(2).ConversionTable(11).Name = "Landed Helicopter (Loads From Right)"
        gaObjects(2).ConversionTable(12).ObjectValue = &HBE
        gaObjects(2).ConversionTable(12).Name = "Landed Helicopter (Loads From Left)"
        gaObjects(2).ConversionTable(13).ObjectValue = &HBC
        gaObjects(2).ConversionTable(13).Name = "Flying Helicopter (Going to helipad)"
        gaObjects(2).ConversionTable(14).ObjectValue = &HBF
        gaObjects(2).ConversionTable(14).Name = "Drop off for POWs"
        gaObjects(2).ConversionTable(15).ObjectValue = &HCE
        gaObjects(2).ConversionTable(15).Name = "Parked Jeep"
        gaObjects(2).ConversionTable(16).ObjectValue = &HD3
        gaObjects(2).ConversionTable(16).Name = "Parked Jeep With Power Up"
        gaObjects(2).ConversionTable(17).ObjectValue = &H29
        gaObjects(2).ConversionTable(17).Name = "Attack Sub"
        gaObjects(2).ConversionTable(18).ObjectValue = &H9B
        gaObjects(2).ConversionTable(18).Name = "Gate"
        gaObjects(2).ConversionTable(19).ObjectValue = &H92
        gaObjects(2).ConversionTable(19).Name = "Spread Turret"
        gaObjects(2).ConversionTable(20).ObjectValue = &HD0
        gaObjects(2).ConversionTable(20).Name = "Power Up-Kill Enemies On Screen"
        gaObjects(2).ConversionTable(21).ObjectValue = &HD1
        gaObjects(2).ConversionTable(21).Name = "Power Up-Full Bazooka"
        gaObjects(2).ConversionTable(22).ObjectValue = &HD2
        gaObjects(2).ConversionTable(22).Name = "Power Up-1Up"
        gaObjects(2).ConversionTable(23).ObjectValue = &H95
        gaObjects(2).ConversionTable(23).Name = "POW Upgrade Barracks Entrance"
        gaObjects(2).Offset(23) = New Point(0, 8)
        gaObjects(2).ConversionTable(24).ObjectValue = &HF0
        gaObjects(2).ConversionTable(24).Name = "Level End Screen Scroll"
        gaObjects(2).ConversionTable(25).ObjectValue = &HF1
        gaObjects(2).ConversionTable(25).Name = "Reload Original level Palette"
        gaObjects(2).ConversionTable(26).ObjectValue = &HF2
        gaObjects(2).ConversionTable(26).Name = "Change Palette"
        gaObjects(2).ConversionTable(27).ObjectValue = &HA5
        gaObjects(2).ConversionTable(27).Name = "Level 3 Boss"

        gaObjects(3).ConversionTable(0).ObjectValue = &H85
        gaObjects(3).ConversionTable(0).Name = "Platform Tank (White)"
        gaObjects(3).Offset(0) = New Point(0, 6)
        gaObjects(3).ConversionTable(1).ObjectValue = &H86
        gaObjects(3).ConversionTable(1).Name = "Platform Tank (Yellow)"
        gaObjects(3).Offset(1) = New Point(0, 6)
        gaObjects(3).ConversionTable(2).ObjectValue = &H1
        gaObjects(3).ConversionTable(2).Name = "Mobile Soldier"
        gaObjects(3).ConversionTable(3).ObjectValue = &H2
        gaObjects(3).ConversionTable(3).Name = "Stationary Soldier"
        gaObjects(3).ConversionTable(4).ObjectValue = &H4
        gaObjects(3).ConversionTable(4).Name = "Mobile Soldier - Can walk in swamp"
        gaObjects(3).ConversionTable(5).ObjectValue = &H2A
        gaObjects(3).ConversionTable(5).Name = "Right-Facing Spread Truck"
        gaObjects(3).ConversionTable(6).ObjectValue = &H2B
        gaObjects(3).ConversionTable(6).Name = "Left-Facing Spread Truck"
        gaObjects(3).ConversionTable(7).ObjectValue = &H9B
        gaObjects(3).ConversionTable(7).Name = "Gate"
        gaObjects(3).ConversionTable(8).ObjectValue = &H93
        gaObjects(3).ConversionTable(8).Name = "POW Right Entrance (2)"
        gaObjects(3).Offset(8) = New Point(4, 0)
        gaObjects(3).ConversionTable(9).ObjectValue = &H7
        gaObjects(3).ConversionTable(9).Name = "Red Tank"
        gaObjects(3).ConversionTable(10).ObjectValue = &H9D
        gaObjects(3).ConversionTable(10).Name = "POW Left Entrance (4)"
        gaObjects(3).Offset(10) = New Point(-4, 0)
        gaObjects(3).ConversionTable(11).ObjectValue = &H9C
        gaObjects(3).ConversionTable(11).Name = "POW Right Entrance (4)"
        gaObjects(3).Offset(11) = New Point(4, 0)
        gaObjects(3).ConversionTable(12).ObjectValue = &H95
        gaObjects(3).ConversionTable(12).Name = "POW Upgrade Barracks Entrance"
        gaObjects(3).Offset(12) = New Point(0, 8)
        gaObjects(3).ConversionTable(13).ObjectValue = &HBD
        gaObjects(3).ConversionTable(13).Name = "Landed Helicopter (Loads From Right)"
        gaObjects(3).ConversionTable(14).ObjectValue = &HBE
        gaObjects(3).ConversionTable(14).Name = "Landed Helicopter (Loads From Left)"
        gaObjects(3).ConversionTable(15).ObjectValue = &HD0
        gaObjects(3).ConversionTable(15).Name = "Power Up-Kill Enemies On Screen"
        gaObjects(3).ConversionTable(16).ObjectValue = &HD1
        gaObjects(3).ConversionTable(16).Name = "Power Up-Full Bazooka"
        gaObjects(3).ConversionTable(17).ObjectValue = &HD2
        gaObjects(3).ConversionTable(17).Name = "Power Up-1Up"
        gaObjects(3).ConversionTable(18).ObjectValue = &HBC
        gaObjects(3).ConversionTable(18).Name = "Flying Helicopter (Going to helipad)"
        gaObjects(3).ConversionTable(19).ObjectValue = &HBF
        gaObjects(3).ConversionTable(19).Name = "Drop off for POWs"
        gaObjects(3).ConversionTable(20).ObjectValue = &HAD
        gaObjects(3).ConversionTable(20).Name = "Sunken Missile Laucnher"
        gaObjects(3).ConversionTable(21).ObjectValue = &H2F
        gaObjects(3).ConversionTable(21).Name = "Left-Falling Rock"
        gaObjects(3).ConversionTable(22).ObjectValue = &H30
        gaObjects(3).ConversionTable(22).Name = "Right-Falling Rock"
        gaObjects(3).ConversionTable(23).ObjectValue = &H35
        gaObjects(3).ConversionTable(23).Name = "Hidden Mine"
        gaObjects(3).ConversionTable(24).ObjectValue = &HAC
        gaObjects(3).ConversionTable(24).Name = "Missile Launcher"
        gaObjects(3).ConversionTable(25).ObjectValue = &H32
        gaObjects(3).ConversionTable(25).Name = "Train Engine (and cars)"
        gaObjects(3).ConversionTable(26).ObjectValue = &HF0
        gaObjects(3).ConversionTable(26).Name = "Level End Screen Scroll"
        gaObjects(3).ConversionTable(27).ObjectValue = &HF1
        gaObjects(3).ConversionTable(27).Name = "Reload Original level Palette"
        gaObjects(3).ConversionTable(28).ObjectValue = &HF2
        gaObjects(3).ConversionTable(28).Name = "Change Palette"
        gaObjects(3).ConversionTable(29).ObjectValue = &HC0
        gaObjects(3).ConversionTable(29).Name = "Level 4 Boss"
        'The train car is its own sprit object, but it explodes immediately without the engine...
        'gaObjects(3).ConversionTable(26).ObjectValue = &H33
        'gaObjects(3).ConversionTable(26).Name = "Train Car"

        gaObjects(4).ConversionTable(0).ObjectValue = &H85
        gaObjects(4).ConversionTable(0).Name = "Platform Tank (White)"
        gaObjects(4).Offset(0) = New Point(0, 6)
        gaObjects(4).ConversionTable(1).ObjectValue = &H86
        gaObjects(4).ConversionTable(1).Name = "Platform Tank (Yellow)"
        gaObjects(4).Offset(1) = New Point(0, 6)
        gaObjects(4).ConversionTable(2).ObjectValue = &H1
        gaObjects(4).ConversionTable(2).Name = "Mobile Soldier"
        gaObjects(4).ConversionTable(3).ObjectValue = &H2
        gaObjects(4).ConversionTable(3).Name = "Stationary Soldier"
        gaObjects(4).ConversionTable(4).ObjectValue = &HE
        gaObjects(4).ConversionTable(4).Name = "Flame Tank"
        gaObjects(4).ConversionTable(5).ObjectValue = &H8C
        gaObjects(4).ConversionTable(5).Name = "Mounted Gun"
        gaObjects(4).ConversionTable(6).ObjectValue = &H94
        gaObjects(4).ConversionTable(6).Name = "POW Left Entrance (2)"
        gaObjects(4).Offset(6) = New Point(-4, 0)
        gaObjects(4).ConversionTable(7).ObjectValue = &H93
        gaObjects(4).ConversionTable(7).Name = "POW Right Entrance (2)"
        gaObjects(4).Offset(7) = New Point(4, 0)
        gaObjects(4).ConversionTable(8).ObjectValue = &H7
        gaObjects(4).ConversionTable(8).Name = "Red Tank"
        gaObjects(4).ConversionTable(9).ObjectValue = &H9D
        gaObjects(4).ConversionTable(9).Name = "POW Left Entrance (4)"
        gaObjects(4).Offset(9) = New Point(-4, 0)
        gaObjects(4).ConversionTable(10).ObjectValue = &H9C
        gaObjects(4).ConversionTable(10).Name = "POW Right Entrance (4)"
        gaObjects(4).Offset(10) = New Point(4, 0)
        gaObjects(4).ConversionTable(11).ObjectValue = &HB
        gaObjects(4).ConversionTable(11).Name = "Silver Tank"
        gaObjects(4).ConversionTable(12).ObjectValue = &HBD
        gaObjects(4).ConversionTable(12).Name = "Landed Helicopter (Loads From Right)"
        gaObjects(4).ConversionTable(13).ObjectValue = &HBE
        gaObjects(4).ConversionTable(13).Name = "Landed Helicopter (Loads From Left)"
        gaObjects(4).ConversionTable(14).ObjectValue = &HBC
        gaObjects(4).ConversionTable(14).Name = "Flying Helicopter (Going to helipad)"
        gaObjects(4).ConversionTable(15).ObjectValue = &HBF
        gaObjects(4).ConversionTable(15).Name = "Drop off for POWs"
        gaObjects(4).ConversionTable(16).ObjectValue = &H99
        gaObjects(4).ConversionTable(16).Name = "Barracks with Tank in it"
        gaObjects(4).Offset(16) = New Point(0, -6)
        gaObjects(4).ConversionTable(17).ObjectValue = &H3A
        gaObjects(4).ConversionTable(17).Name = "Attack Plane - Specific"
        gaObjects(4).ConversionTable(18).ObjectValue = &H3B
        gaObjects(4).ConversionTable(18).Name = "Attack Plane - Anywhere"
        gaObjects(4).ConversionTable(19).ObjectValue = &H9B
        gaObjects(4).ConversionTable(19).Name = "Gate"
        gaObjects(4).ConversionTable(20).ObjectValue = &H3
        gaObjects(4).ConversionTable(20).Name = "Flame Infantry"
        gaObjects(4).ConversionTable(21).ObjectValue = &HD0
        gaObjects(4).ConversionTable(21).Name = "Power Up-Kill Enemies On Screen"
        gaObjects(4).ConversionTable(22).ObjectValue = &HD1
        gaObjects(4).ConversionTable(22).Name = "Power Up-Full Bazooka"
        gaObjects(4).ConversionTable(23).ObjectValue = &HD2
        gaObjects(4).ConversionTable(23).Name = "Power Up-1Up"
        gaObjects(4).ConversionTable(24).ObjectValue = &H95
        gaObjects(4).ConversionTable(24).Name = "POW Upgrade Barracks Entrance"
        gaObjects(4).Offset(24) = New Point(0, -6)
        gaObjects(4).ConversionTable(25).ObjectValue = &H35
        gaObjects(4).ConversionTable(25).Name = "Hidden mine"
        gaObjects(4).ConversionTable(26).ObjectValue = &HF0
        gaObjects(4).ConversionTable(26).Name = "Level End Screen Scroll"
        gaObjects(4).ConversionTable(27).ObjectValue = &HF1
        gaObjects(4).ConversionTable(27).Name = "Reload Original level Palette"
        gaObjects(4).ConversionTable(28).ObjectValue = &HF2
        gaObjects(4).ConversionTable(28).Name = "Change Palette"
        gaObjects(4).ConversionTable(29).ObjectValue = &H97
        gaObjects(4).ConversionTable(29).Name = "Level 5 Boss"

        gaObjects(5).ConversionTable(0).ObjectValue = &H85
        gaObjects(5).ConversionTable(0).Name = "Platform Tank (White)"
        gaObjects(5).Offset(0) = New Point(0, 6)
        gaObjects(5).ConversionTable(1).ObjectValue = &H86
        gaObjects(5).ConversionTable(1).Name = "Platform Tank (Yellow)"
        gaObjects(5).Offset(1) = New Point(0, 6)
        gaObjects(5).ConversionTable(2).ObjectValue = &H1
        gaObjects(5).ConversionTable(2).Name = "Mobile Soldier"
        gaObjects(5).ConversionTable(3).ObjectValue = &H2
        gaObjects(5).ConversionTable(3).Name = "Stationary Soldier"
        gaObjects(5).ConversionTable(4).ObjectValue = &HD3
        gaObjects(5).ConversionTable(4).Name = "Parked Jeep With Power Up"
        gaObjects(5).ConversionTable(5).ObjectValue = &HF
        gaObjects(5).ConversionTable(5).Name = "Enemy Jeep"
        gaObjects(5).ConversionTable(6).ObjectValue = &H94
        gaObjects(5).ConversionTable(6).Name = "POW Left Entrance (2)"
        gaObjects(5).Offset(6) = New Point(-4, 0)
        gaObjects(5).ConversionTable(7).ObjectValue = &H93
        gaObjects(5).ConversionTable(7).Name = "POW Right Entrance (2)"
        gaObjects(5).Offset(7) = New Point(4, 0)
        gaObjects(5).ConversionTable(8).ObjectValue = &H7
        gaObjects(5).ConversionTable(8).Name = "Red Tank"
        gaObjects(5).ConversionTable(9).ObjectValue = &H9D
        gaObjects(5).ConversionTable(9).Name = "POW Left Entrance (4)"
        gaObjects(5).Offset(9) = New Point(-4, 0)
        gaObjects(5).ConversionTable(10).ObjectValue = &H9C
        gaObjects(5).ConversionTable(10).Name = "POW Right Entrance (4)"
        gaObjects(5).Offset(10) = New Point(4, 0)
        gaObjects(5).ConversionTable(11).ObjectValue = &H3A
        gaObjects(5).ConversionTable(11).Name = "Attack Plane - Specific"
        gaObjects(5).ConversionTable(12).ObjectValue = &H3B
        gaObjects(5).ConversionTable(12).Name = "Attack Plane - Anywhere"
        gaObjects(5).ConversionTable(13).ObjectValue = &HBD
        gaObjects(5).ConversionTable(13).Name = "Landed Helicopter (Loads From Right)"
        gaObjects(5).ConversionTable(14).ObjectValue = &HBE
        gaObjects(5).ConversionTable(14).Name = "Landed Helicopter (Loads From Left)"
        gaObjects(5).ConversionTable(15).ObjectValue = &HBC
        gaObjects(5).ConversionTable(15).Name = "Flying Helicopter (Going to helipad)"
        gaObjects(5).ConversionTable(16).ObjectValue = &HBF
        gaObjects(5).ConversionTable(16).Name = "Drop off for POWs"
        gaObjects(5).ConversionTable(17).ObjectValue = &H43
        gaObjects(5).ConversionTable(17).Name = "Attack Helicopter"
        gaObjects(5).ConversionTable(18).ObjectValue = &HAE
        gaObjects(5).ConversionTable(18).Name = "Missle Launcher Bay Door"
        gaObjects(5).ConversionTable(19).ObjectValue = &HCE
        gaObjects(5).ConversionTable(19).Name = "Parked Jeep/Tank"
        gaObjects(5).ConversionTable(20).ObjectValue = &HC4
        gaObjects(5).ConversionTable(20).Name = "Escalator"
        gaObjects(5).ConversionTable(21).ObjectValue = &H9B
        gaObjects(5).ConversionTable(21).Name = "Gate"
        gaObjects(5).ConversionTable(22).ObjectValue = &H92
        gaObjects(5).ConversionTable(22).Name = "Spread Turret"
        gaObjects(5).ConversionTable(23).ObjectValue = &HD0
        gaObjects(5).ConversionTable(23).Name = "Power Up-Kill Enemies On Screen"
        gaObjects(5).ConversionTable(24).ObjectValue = &HD1
        gaObjects(5).ConversionTable(24).Name = "Power Up-Full Bazooka"
        gaObjects(5).ConversionTable(25).ObjectValue = &HD2
        gaObjects(5).ConversionTable(25).Name = "Power Up-1Up"
        gaObjects(5).ConversionTable(26).ObjectValue = &HF0
        gaObjects(5).ConversionTable(26).Name = "Level End Screen Scroll"
        gaObjects(5).ConversionTable(27).ObjectValue = &HF1
        gaObjects(5).ConversionTable(27).Name = "Reload Original level Palette"
        gaObjects(5).ConversionTable(28).ObjectValue = &HF2
        gaObjects(5).ConversionTable(28).Name = "Change Palette"
        gaObjects(5).ConversionTable(29).ObjectValue = &HC5
        gaObjects(5).ConversionTable(29).Name = "Level 6 Boss Turrets"
        gaObjects(5).ConversionTable(30).ObjectValue = &HC9
        gaObjects(5).ConversionTable(30).Name = "Level 6 Boss HQ Bldg"
    End Sub

    ''' <summary>
    ''' Builds the arrays that define whether each tile in each level uses the standard tile set or the palette set after the F2 object has done its thing
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub BuildF2TileArray()
        'Initialize
        For i As Integer = 0 To &HFF
            mabolF2Tiles(0, i) = False
            mabolF2Tiles(1, i) = False
            mabolF2Tiles(2, i) = False
            mabolF2Tiles(3, i) = False
            mabolF2Tiles(4, i) = False
            mabolF2Tiles(5, i) = False
        Next i

        'Level 1 Tiles - no F2 tiles
        'level 2 Tiles - no F2 tiles

        'Level 3 F2 Tiles
        mabolF2Tiles(2, &H61) = True
        mabolF2Tiles(2, &H62) = True
        mabolF2Tiles(2, &H63) = True
        mabolF2Tiles(2, &H65) = True
        mabolF2Tiles(2, &H66) = True
        mabolF2Tiles(2, &H67) = True
        mabolF2Tiles(2, &H68) = True
        mabolF2Tiles(2, &H69) = True
        mabolF2Tiles(2, &H6A) = True
        mabolF2Tiles(2, &H6B) = True
        mabolF2Tiles(2, &H6C) = True
        mabolF2Tiles(2, &H6D) = True
        mabolF2Tiles(2, &H6E) = True
        mabolF2Tiles(2, &H6F) = True
        mabolF2Tiles(2, &H70) = True
        mabolF2Tiles(2, &H74) = True

        'Level 4 Tiles - no F2 tiles

        'Level 5 F2 Tiles
        mabolF2Tiles(4, &H5F) = True
        mabolF2Tiles(4, &H60) = True
        mabolF2Tiles(4, &H61) = True
        mabolF2Tiles(4, &H62) = True
        mabolF2Tiles(4, &H63) = True
        mabolF2Tiles(4, &H64) = True
        mabolF2Tiles(4, &H65) = True
        mabolF2Tiles(4, &H66) = True
        mabolF2Tiles(4, &H67) = True
        mabolF2Tiles(4, &H68) = True
        mabolF2Tiles(4, &H69) = True
        mabolF2Tiles(4, &H6A) = True
        mabolF2Tiles(4, &H6B) = True
        mabolF2Tiles(4, &H6C) = True
        mabolF2Tiles(4, &H6D) = True
        mabolF2Tiles(4, &H6E) = True
        mabolF2Tiles(4, &H6F) = True
        mabolF2Tiles(4, &H70) = True
        mabolF2Tiles(4, &H71) = True
        mabolF2Tiles(4, &H72) = True
        mabolF2Tiles(4, &H73) = True
        mabolF2Tiles(4, &H74) = True
        mabolF2Tiles(4, &H75) = True
        mabolF2Tiles(4, &H76) = True
        mabolF2Tiles(4, &H77) = True
        mabolF2Tiles(4, &H78) = True
        mabolF2Tiles(4, &H79) = True
        mabolF2Tiles(4, &H7A) = True
        mabolF2Tiles(4, &H7B) = True

        'Level 6 F2 Tiles
        mabolF2Tiles(5, &H19) = True
        mabolF2Tiles(5, &H1A) = True
        mabolF2Tiles(5, &H4B) = True
        mabolF2Tiles(5, &H70) = True
        mabolF2Tiles(5, &H73) = True
        mabolF2Tiles(5, &H74) = True
        mabolF2Tiles(5, &H75) = True
        mabolF2Tiles(5, &H76) = True
        mabolF2Tiles(5, &H77) = True
        mabolF2Tiles(5, &H78) = True
        mabolF2Tiles(5, &H79) = True
        mabolF2Tiles(5, &H7A) = True
        mabolF2Tiles(5, &H7B) = True
        mabolF2Tiles(5, &H7C) = True
        mabolF2Tiles(5, &H7E) = True
        mabolF2Tiles(5, &H7F) = True
        mabolF2Tiles(5, &H80) = True
        mabolF2Tiles(5, &H81) = True
        mabolF2Tiles(5, &H82) = True
        mabolF2Tiles(5, &H83) = True
    End Sub

    ''' <summary>
    ''' Increments the size of an array
    ''' </summary>
    ''' <param name="Arr">the array who's size to increase</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function AutoRedim(ByRef Arr() As ObjectStruct) As Integer
        If Arr Is Nothing Then
            AutoRedim = 0
        Else
            AutoRedim = UBound(Arr) + 1
        End If

        ReDim Preserve Arr(AutoRedim)
    End Function
#End Region
End Class
